Skype for Business App SDK 紹介


Skype for Business API 開発

こんにちは。

今回は、最近ダウンロード開始された Skype for Business App SDK について、実際の code を使って紹介します。

補足 : ここでは Objective-C を使用しますが、Swift でも書けます。ただし、後述の Helper クラスは Objective-C しかないので、Swift で Helper と同様の処理を頑張って記述する必要があります。

Skype for Business App SDK で可能なこと

以前紹介した Skype Web SDK は Skype for Business REST API (UCWA, User API) を使った JavaScript API (Web 用の API) でしたが、ここで紹介する Skype for Business App SDK は iOS, Android 用の SDK です。

ただし、利用シナリオが限定されているので注意してください。
Skype Web SDK では Skype for Business の一般シナリオである B2B での利用を想定していましたが、この Skype for Business App SDK は B2C での利用を想定しています
Skype for Business を持っていない一般ユーザーが、Skype for Business を持っている裏側のアドバイザー、医者、ヘルプデスクなど (スタッフ) と接続して連携するような使い方で、以前、似たシナリオで Skype URI や Skype Button を使った開発手法を紹介しましたが (「Skype URI で Skype for Business に Call する」を参照)、今回のシナリオでは Skype さえも不要で、iOS や Android で動く一般のアプリに埋め込まれた形で使用できます。(利用者は、それ以外の追加のアプリなどは不要です。)

B2C シナリオの設計(Design)

本投稿では開発に踏み切る方のハードルを上げないよう、以降では極力簡単なサンプルで紹介しますが、実は、この Skype for Business App SDK を使用するには、そこそこちゃんとした設計 (構成) が必要です。

というのは、上述の通り B2C シナリオのため利用者 (User) は匿名で利用しますが、この匿名アクセスを実現するために「Skype Meeting を API (REST, Web) で処理する (Skype for Business)」で紹介した Online Meeting を組み合わせるためです。(組み合わせ方がいろいろ考えられます。)
Skype for Business の Online Meeting では、Skype for Business のライセンスのない匿名ユーザーも会議に参加できますが (会議の設定により、匿名アクセスを拒否したり、ロビーで承認したユーザーのみアクセスさせることなども可能)、Skype for Business App SDK は、この仕組みを使って Skype for Business を持っていない一般ユーザーと Skype for Business を持っているバックエンドのスタッフをつなぎます。(Skype for Business App SDK に Meeting Url を渡すことで、この接続をおこなってくれます。)

From "MSDN - Embed Skype business-to-consumer communications in your mobile app" (Richard Taylor, Microsoft)

さまざまな方法 (シナリオ) が考えられますが、一例を紹介すると、例えば下記のような処理をおこなう一連のアプリ (フロントエンド アプリ、バックエンド アプリ) を提供します。

  1. 顧客からの質問に応答するバックエンド スタッフ (Skype for Business のライセンスを持ったユーザー) は、カスタム アプリ (ISV アプリ) を使って、内部で、「Skype Meeting を API (REST, Web) で処理する (Skype for Business)」で解説した方法で Scheduled Meeting を作成しておきます。(顧客からの問い合わせを待機した状態)
  2. 顧客がモバイル アプリ (スマホ アプリ) を使って問い合わせをおこなうと、アプリのサーバー側では、利用可能なバックエンド スタッフの Scheduled Meeting (上述) を検索して、そのうちの 1 つの Meeting Url をアプリ (スマホ アプリ) に返します。
  3. スマホ アプリ (iOS, Android) は、Skype for Business App SDK を使って、このバックエンド スタッフ (Skype for Business User) と顧客 (Anonymous の Guest User) を接続します。

こうした方法以外に、「Skype Meeting を API (REST, Web) で処理する (Skype for Business)」で解説した Ad-hoc Meeting を使うパターンも考えられます。
認証フローに注意しながら、アプリのニーズに応じて、さまざまな方法で設計できます。

補足 :  Skype for Business Online では、Azure AD : Backend Server-Side アプリの開発 (Deamon, Service など)」で解説した App-only token を使った処理に対応しました。この方法を使って、ログイン画面を使用せず、バックエンドで Online Meeting を作成することも可能です。(2016/07 更新)
なお現在、Skype for Business Online (REST) は「Azure AD : Backend Server-Side アプリの開発 (Deamon, Service など)」で解説した方法をサポートしていません。もし将来、この方式がサポートされれば、さらに接続シナリオは増えていくことでしょう。

アプリの構築 - Meeting Url の取得

では、実際に簡単なサンプルを構築してみましょう。
上述の通り、本来なら、サーバー側 (バックエンド) のプログラミングなども必要ですが、サンプルを煩雑にしない目的で、今回は上述の 3 の処理のみをプログラミングします。(1 と 2 は「Skype Meeting を API (REST, Web) で処理する (Skype for Business)」で解説した API で実装されているものと仮定します。)

補足 (2017/04/13 追記) : サーバーサイドで Ad-hoc Online Meeting を作成する Trusted Application API のサンプルを掲載しました。「Trusted Application API (Skype for Business) – Authentication and Online Meetings」を参照してください。

そこで、このあとのプログラミングのため、あらかじめ Meeting Url を手動で作成して、この Meeting Url を使って、Skype for Business App SDK による Video 接続の iOS アプリ (サンプル) を作成します。
使用する Meeting Url は「Skype Meeting を API (REST, Web) で処理する (Skype for Business)」で解説した Join Url で、「https://meet.lync.com/o365directory/tsmatsuz/3O719UCX」といった形式です。
PC や Mac をお使いの方は、Office 365 の試用版などで Outlook (Web 版の Outlook Web App も可) にログインして Skype 会議 (Skype Meeting) を作成することで、下記の赤の囲みの通り、簡単に Meeting Url が取得できます。

なお、作成された Skype Meeting の既定の設定では、Meeting Url さえ分かっていれば匿名 (Guest) ユーザーで参加可能ですが、Skype Meeting の設定 (管理) 画面でこの権限の確認・変更が可能です。

アプリの構築 - XCode プロジェクトの作成・構成

では、上記で取得した Meeting Url を使って、モバイル アプリを作成してみましょう。

XCode でプロジェクトを新規作成します。(今回は [Single View Project] を作成してみましょう。)

Skype for Business App SDK をダウンロードし、上記で作成した XCode プロジェクトの [Embedded Binary] に、SDK の AppSDKiOS/SkypeForBusiness.framework を追加します。

補足 : BUILD と RUN における SDK 利用の注意点
今回紹介する Video のサンプルは、Mac 上の iOS Simulator では動作 (RUN) しません。必ず、iPhone など実機を接続して動作確認してください。
なお、iOS Simulator で上記の AppSDKiOS/SkypeForBusiness.framework を使用した場合、BUILD も通りません。("missing required architecture x86_x64" のエラーが発生します。) もし、開発中、iOS Simulator の構成で BUILD をしたい場合は、一時的に AppSDKiOSSimulator/SkypeForBusiness.framework のほうを使用してください。(上述の通り、動作確認 (RUN) では Simulator は使用できないので注意。)

また今回、SDK に含まれるヘルパークラス (SfBConversationHelper など) を使用するため、Skype for Business App SDK の Helpers/SfBConversationHelper.h、Helpers/SfBConversationHelper.m をプロジェクトのソースに含めておきます。(下図)

さいごに、必要な XCode のフレームワークを追加します。
プロジェクトの [Linked Frameworks and Libraries] を表示して、今回は、AVFoundation.framework と GLKit.framework (このあと GLKitView を使います) を追加します。

アプリの構築 - UI の作成

XCode の Storyboard を使って UI を作成します。
今回のサンプルでは、単に、アプリ起動時に Video 会議に接続して、相手の顔 (Incoming Video) と自分の顔 (Outgoing Video) を表示しますので、ボタン遷移などの Storyboard らしいデザインは不要です。

プロジェクトに含まれる Main.Storyboard で、既定の [View Controller Scene] を表示して、下図の通り、Incoming Video (相手の顔) を表示するための GLKitView (下図の薄い水色の部分) と、その中に Outgoing Video (自分の顔) を表示する UIView (下図の白い部分) を Drag & Drop で挿入します。

このあと、プログラム コード (Objective-C) を使って、この挿入した View や Layout にストリーミングを関連付けることで Video が表示されます。
そこで、あらかじめ、これらの挿入した View を、Control キーを押しながら View のソースコード (ViewController.m) の @interface と @end の間に Drag & Drop して、下記太字の通りサブクラスを作成します。

#import "ViewController.h"
#import <GLKit/GLKit.h>

@interface ViewController () <SfBConversationHelperDelegate>

@property (strong, nonatomic) IBOutlet GLKView *incomingView;
@property (strong, nonatomic) IBOutlet UIView *outgoingView;

@end

@implementation ViewController
. . .

@end

補足 : 上記ソースコードの通り、GLKit.h のヘッダー参照も追加 (import) しておいてください。

アプリの構築 - プログラミング

以上で準備完了です。
以降は、Skype for Business App SDK を使ってプログラム コードを作成してみましょう。

まず、Skype for Business App SDK を使用するため、前述で挿入したバイナリー (SkypeForBusiness.framework) や挿入したヘルパークラス (SfBConversationHelper) へのヘッダー参照を追加 (import) します。

#import "ViewController.h"
#import "SfBConversationHelper.h"
#import <GLKit/GLKit.h>
#import <SkypeForBusiness/SkypeForBusiness.h>

@interface ViewController () <SfBConversationHelperDelegate>
. . .

@end

@implementation ViewController
. . .

@end

今回は単純に、上述で取得した Meeting Url に匿名 User (Guest User) で接続し、アプリ起動時 (View の表示時) に、上記で挿入した View に Video を表示します。ViewController の表示が完了した段階 (viewDidAppear) でこの処理をおこなうため、下記太字の通りソースコードを追加します。

なお、「https://meet.lync.com/o365directory/tsmatsuz/3O719UCX」は上記であらかじめ取得した Meeting Url で、「Demo Customer」は Meeting に Guest 参加 (Anonymous 参加) した際の参加者の名前です。

#import "ViewController.h"
#import "SfBConversationHelper.h"
#import <GLKit/GLKit.h>
#import <SkypeForBusiness/SkypeForBusiness.h>

@interface ViewController () <SfBConversationHelperDelegate>

@property (strong, nonatomic) IBOutlet GLKView *incomingView;
@property (strong, nonatomic) IBOutlet UIView *outgoingView;
@property (strong, nonatomic) SfBConversationHelper *conversationHelper;

@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

- (void)viewDidAppear:(BOOL)animated {
  [self joinMeeting];
}

/**
 *  Joins a meeting
 */
- (void)joinMeeting {
  NSError *error = nil;
  
  NSString *meetingURLString = @"https://meet.lync.com/o365directory/tsmatsuz/3O719UCX";
  NSString *meetingDisplayName = @"Demo Customer";
  
  // Join online meeting
  SfBApplication *sfb = SfBApplication.sharedApplication;
  SfBConversation *conversation = [sfb
    joinMeetingAnonymousWithUri:[NSURL URLWithString:meetingURLString]
    displayName:meetingDisplayName
    error:&error];
          
  if (conversation) {
    // Success : Set observer and show video in UI View
    [conversation addObserver:self
      forKeyPath:@"canLeave"
      options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
      context:nil];
    _conversationHelper = [[SfBConversationHelper alloc]
      initWithConversation:conversation
      delegate:self
      devicesManager:sfb.devicesManager
      outgoingVideoView:self.outgoingView
      incomingVideoLayer:(CAEAGLLayer *) self.incomingView.layer
      userInfo:@"Demo Customer"];
  } else {
    // Fail : Show error details
    UIAlertController *alertController = [UIAlertController
      alertControllerWithTitle:@"Error"
      message:error.localizedDescription
      preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:@"Close"
      style:UIAlertActionStyleCancel
      handler:nil]];
    [self presentViewController:alertController animated:YES completion:nil];
  }
}

@end

今回は Video の処理のため、Video の状態通知の際に呼ばれる、下記の 3 つの Callback Listener も実装します。

#import "ViewController.h"
#import "SfBConversationHelper.h"
#import <GLKit/GLKit.h>
#import <SkypeForBusiness/SkypeForBusiness.h>

@interface ViewController () <SfBConversationHelperDelegate>

@property (strong, nonatomic) IBOutlet GLKView *incomingView;
@property (strong, nonatomic) IBOutlet UIView *outgoingView;
@property (strong, nonatomic) SfBConversationHelper *conversationHelper;

@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

- (void)viewDidAppear:(BOOL)animated {
  [self joinMeeting];
}

/**
 *  Joins a meeting
 */
- (void)joinMeeting {
  . . .
  
}

/**
 *  Callback listner
 */

- (void)conversationHelper:(SfBConversationHelper *)avHelper didSubscribeToVideo:(SfBParticipantVideo *)video {
  // Do some task at incoming video
}

- (void)conversationHelper:(SfBConversationHelper *)avHelper videoService:(SfBVideoService *)videoService didChangeCanStart:(BOOL)canStart {
  // When video service is ready to start, start the service.
  if (canStart) {
    [videoService start:nil];
  }
}

- (void)conversationHelper:(SfBConversationHelper *)avHelper selfAudio:(SfBParticipantAudio *)audio didChangeIsMuted:(BOOL)isMuted {
  // When the audio status changes, do some task (ex. change mute/unmute button)
  if (!isMuted) {
    // Do some task
  }
  else {
    // Do some task
  }
}

@end

さいごに、今回、canLeave プロパティの observe もおこなうようにしたので (上記コード参照)、その Handler も追加しておきます。

#import "ViewController.h"
#import "SfBConversationHelper.h"
#import <GLKit/GLKit.h>
#import <SkypeForBusiness/SkypeForBusiness.h>

@interface ViewController () <SfBConversationHelperDelegate>

@property (strong, nonatomic) IBOutlet GLKView *incomingView;
@property (strong, nonatomic) IBOutlet UIView *outgoingView;
@property (strong, nonatomic) SfBConversationHelper *conversationHelper;

@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

- (void)viewDidAppear:(BOOL)animated {
  [self joinMeeting];
}

/**
 *  Joins a meeting
 */
 
. . .

/**
 *  Callback listner
 */

. . .

/**
 *  Monitor canLeave property of a conversation (Handler)
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {  
  if ([keyPath isEqualToString:@"canLeave"]) {
    // Do some task
  }
}

@end

今回は実装しませんが、もちろん、Video の Mute / Unmute や、会話の終了などの基本的な操作も可能です。

// Leave from conversation
NSError *error = nil;
[_conversationHelper.conversation leave:&error];

// Toggle Mute/Unmute audio
[_conversationHelper toggleAudioMuted:nil];

以上で完成です。
実行結果は説明不要と思いますが、アプリを起動すると Online Meeting に Anonymous (Guest) で参加されて、下図の通り表示されます。(本田さんにご協力いただきました。。。) なお、上述の通り、Mac 上の Simulator ではなく、実機で動作を確認する必要があるので注意してください。

さいごに、今回作成したソースコード全体を掲載しておきます。今回は Objective-C ですが、Swift で記述した場合、上述の通り、Helper が使用できないため、もっと長くなります。

#import "ViewController.h"
#import "SfBConversationHelper.h"
#import <GLKit/GLKit.h>
#import <SkypeForBusiness/SkypeForBusiness.h>

@interface ViewController () <SfBConversationHelperDelegate>

@property (strong, nonatomic) IBOutlet GLKView *incomingView;
@property (strong, nonatomic) IBOutlet UIView *outgoingView;
@property (strong, nonatomic) SfBConversationHelper *conversationHelper;

@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

- (void)viewDidAppear:(BOOL)animated {
  [self joinMeeting];
}

/**
 *  Joins a meeting
 */
- (void)joinMeeting {
  NSError *error = nil;
  
  NSString *meetingURLString = @"https://meet.lync.com/o365directory/tsmatsuz/3O719UCX";
  NSString *meetingDisplayName = @"Demo Customer";
  
  // Join online meeting
  SfBApplication *sfb = SfBApplication.sharedApplication;
  SfBConversation *conversation = [sfb
    joinMeetingAnonymousWithUri:[NSURL URLWithString:meetingURLString]
    displayName:meetingDisplayName
    error:&error];
          
  if (conversation) {
    // Success : Set observer and show video in UI View  
    [conversation addObserver:self
      forKeyPath:@"canLeave"
      options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
      context:nil];
    _conversationHelper = [[SfBConversationHelper alloc]
      initWithConversation:conversation
      delegate:self
      devicesManager:sfb.devicesManager
      outgoingVideoView:self.outgoingView
      incomingVideoLayer:(CAEAGLLayer *) self.incomingView.layer
      userInfo:@"Demo Customer"];
  } else {
    // Fail : Show error details
    UIAlertController *alertController = [UIAlertController
      alertControllerWithTitle:@"Error"
      message:error.localizedDescription
      preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:@"Close"
      style:UIAlertActionStyleCancel
      handler:nil]];
    [self presentViewController:alertController animated:YES completion:nil];
  }
}

/**
 *  Callback listner
 */

- (void)conversationHelper:(SfBConversationHelper *)avHelper didSubscribeToVideo:(SfBParticipantVideo *)video {
  // Do some task at incoming video
  //self.incomingView.hidden = NO;
}

- (void)conversationHelper:(SfBConversationHelper *)avHelper videoService:(SfBVideoService *)videoService didChangeCanStart:(BOOL)canStart {
  // When video service is ready to start, start the service.
  if (canStart) {
    //if (self.outgoingView.hidden) {
    //  self.outgoingView.hidden = NO;
    //}
    [videoService start:nil];
  }
}

- (void)conversationHelper:(SfBConversationHelper *)avHelper selfAudio:(SfBParticipantAudio *)audio didChangeIsMuted:(BOOL)isMuted {
  // When the audio status changes, do some task (ex. change mute/unmute button)
  if (!isMuted) {
    // Do some task
//    [self.muteButton setTitle:@"Unmute" forState:UIControlStateNormal];
  }
  else {
    // Do some task
//    [self.muteButton setTitle:@"Mute" forState:UIControlStateNormal];
  }
}

/**
 *  Monitor canLeave property of a conversation (Handler)
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {  
  if ([keyPath isEqualToString:@"canLeave"]) {
    // Do some task
    //self.endCallButton.enabled = _conversationHelper.conversation.canLeave;
  }
}

@end
Comments (1)

  1. natra says:

    I get this issue when running project
    “dyld: Library not loaded: @rpath/SkypeForBusiness.framework/SkypeForBusiness
    Referenced from: /var/containers/Bundle/Application/9D341720-04FA-4978-A76A-8BC3ADC5795E/SkypeTest.app/SkypeTest
    Reason: image not found
    (lldb) ”
    how can i resolve this issue?
    Thank!

Skip to main content