SlideShare ist ein Scribd-Unternehmen logo
1 von 31
Downloaden Sie, um offline zu lesen
Cast SDK for Flutter
山田 幸司
koji.yamada@gree.net
グリー株式会社
開発本部 / インフラストラクチャ部 / ディベロップメント
オペレーションズグループ / サービスディベロップメント
チーム 所属
業務内容
VRアプリフロントエンド担当
最近のお仕事はFlutter / Unity / Unreal Engine 4など
アジェンダ
● Chromecastについて
○ Chromecastとは?
○ Cast SDKについて
● Cast SDK for Flutterを作る
○ Flutterでプラグインを作る準備やインストール方法など
○ 構成から実装のおおまかな流れ
○ 実装方法などについて
Chromecast
● Googleが開発・販売する小型のデバイス
● HDMI端子に接続/Wi-Fiを介してスマートフォンやタブレットなどで再生して
いる動画、写真、ウェブサイトなどをディスプレイに表示出来るデバイス
● ミラーリングとはやや違ってスマートフォンをコントローラとして扱い動画
の再生や停止などもできる
● 値段は5,000円程度(4K対応のUltraは10,000円程度)
● アプリで利用するにはCast SDKを組み込む必要がある
※↑アプリが対応しているかつ周辺にChromecast
がある場合はこのアイコンが表示される
Cast SDK
● Googleが提供するChromecast用のSDK
○ https://developers.google.com/cast/
● Android/iOS/Chrome(ブラウザ)に対応
○ Android → Java/Kotlin
○ iOS → Objective-C/Swift
○ Chrome → Javascript
● 公式のFlutter用Cast SDKはまだ存在しない
● Flutter版もいつか対応するかも
○ GitHubのissuesはあがっている
○ https://github.com/flutter/flutter/issues/18212
Cast SDK for Flutter
● 今日の話
○ Flutter用のChromecast Pluginを作る話
○ ChromecastのAndroid/iOS両方に対応するアプリをさく
っと作れる
● 実装するもの
○ 送信側(Sender)と受信側(Receiver)を作る必要があ
りますが、今回はSenderアプリのみについて
○ 動画コントロールパネルやキャストボタンなどのUI部分
はFlutterで実装
○ 動画をChromecastにキャスト/再生と停止ができる
準備
● Plugin Packageプロジェクトの作成
○ $ flutter create --template=plugin -i swift -a kotlin [プロジェクト名]
例) chrome_cast_plugin
Android/build.gradle
…
dependencies {
implementation 'com.google.android.gms:play-services-cast-framework:16.1.2'
...
iOS/Podfile
…
target 'Runner' do
pod 'google-cast-sdk', '~> 4.3'
...
Sync Now
pod install
構成
app
main.dart
lib
chrome_cast_
plugin.dart
Flutter/Dart
Android/Kotlin
iOS/Swift
CastOptionsProvider.kt
ChromeCastPlugin.kt
SwiftChromeCast
Plugin.swift
Cast SDK for Android
gms:play-services-cast
Cast SDK for iOS
google-cast-sdk
● main.dart
○ キャストボタンなどのUI表示
● chrome_cast_plugin.dart
○ Kotlin/Swiftとのつなぎ
プラグイン側
Pluginで実装すること
1. Cast Contextの初期化
2. Cast Dialogの表示
3. Cast Buttonの表示
4. リスナーの登録
5. 動画をキャストする
6. キャストした動画を制御する
Cast Contextの初期化
● Chromecast の機能を使うために必要
● Android
○ OptionsProviderインタフェースを実装したクラスの作成
○ レシーバーIdのCastOptionsのインスタンスを作る
○ CastContext.getSharedInstance(Activity)で初期化
● iOS
○ レシーバーIdのGCKCastOptionのインスタンスを作る
○ GCKCastContext.setSharedInstanceWith(GCKCastOption)で初期化
● 初期化タイミング
○ pluginのregisterWith / register
class ChromeCastPlugin(private val pActivity: Activity, private val pChannel:
MethodChannel):
…
companion object {
@JvmStatic
fun registerWith(registrar: Registrar) {
CastContext.getSharedInstance(pActivity)
…
Android/Kotlin
static const MethodChannel _channel =
const MethodChannel('chrome_cast_plugin');
chrome_cast_plugin.dart
public class SwiftChromeCastPlugin: NSObject, FlutterPlugin {
…
public static func register(with registrar: FlutterPluginRegistrar) {
let tCriteria = GCKDiscoveryCriteria(applicationID: [レシーバーのId])
let tOption = GCKCastOptions(discoveryCriteria: tCriteria)
GCKCastContext.setSharedInstanceWith(tOption)
…
iOS/Swift
class CastOptionsProvider : OptionsProvider {
…
override fun getCastOptions(context: Context): CastOptions? {
val tAppId = context.resources.getIdentifier("chromecast_app_id", "string", context.packageName)
…
return CastOptions.Builder()
.setReceiverApplicationId(context.getString(tAppId))
.build()
}
…
}
Android/Kotlin
Cast Contextの初期化(Android CastOptionsProvider.kt)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="chromecast_app_id">[レシーバーId]</string>
</resources>
app/res/values/string.xml
Cast Dialogの表示
● キャストしてないとき
○ キャスト可能なデバイス一覧を表示
● キャストしてるとき
○ メディア情報、音量などを表示
● Android(少々手間がかかる)
○ 現在キャスト中かどうかを判定して出し分けが必要
○ 現在のキャストセッションがあれば
MediaRouteControllerDialog
○ 無い場合はMediaRouteChooserDialogを呼ぶ
● iOS
○ presentCastDialogを呼ぶだけ
private fun showDialog(pCall: MethodCall, pResult: Result) {
val tCastSession = CastContext.getSharedInstance()?.sessionManager?.currentCastSession
if (tCastSession != null) {
val tControllerDialog = MediaRouteControllerDialog(pActivity,
R.style.CastControllerDialogTheme)
tControllerDialog.show()
} else {
val tChooserDialog = MediaRouteChooserDialog(pActivity, R.style.CastMediaRouterTheme)
tChooserDialog.routeSelector = CastContext.getSharedInstance()?.mergedSelector!!
tChooserDialog.show()
}
}
Android/Kotlin
Future<void> showCastDialog() async {
await _channel.invokeMethod('ShowCastDialog');
}
chrome_cast_plugin.dart
private func showDialog(_ pCall: FlutterMethodCall, _ pResult: @escaping FlutterResult) {
let tContext:GCKCastContext = GCKCastContext.sharedInstance()
tContext.presentCastDialog()
}
iOS/Swift
動画をキャストする
● 動画をキャストするために必要な情報をFlutterからPlugin側に渡す
● 手順
○ [1] キャストするための必要なメタデータ(Metadata)を作成するためのパ
ラメータ
■ タイトル、キャストダイアログに出す画像など
○ [2] 作成したメタデータからメディア情報(MediaInfo)を作成するためのパ
ラメータ
■ 動画のコンテンツタイプ(mp4など)、レシーバーに渡す独自のJsonデータなど
○ [3] 必要に応じて読み込み時のオプションを作成するためのパラメータ
■ 再生位置、自動再生するかどうかなど
○ [4] 動画をデバイスにキャスト
Future<bool> startCast(Media media) async {
bool _result =
await _channel.invokeMethod('StartCast', {
'Uri': media.url,
'Title': media.title,
'Subtitle': media.subTitle,
'Studio': media.studio,
'ContentType': media.contentType,
'Images': {
'Dialog': {
'Url': media.dialogThumbnail.url,
'Width': media.dialogThumbnail.width,
'Height': media.dialogThumbnail.height
},
'Notification': {
'Url': media.notificationThumbnail.url,
}
},
chrome_cast_plugin.dart
'IsAuto': media.isAuto,
'PlayPosition': media.position,
'IsLive': media.isLive,
'CustomData': media.customData
});
return _result;
}
...
class Media {
Media({
this.url,
this.title,
this.subTitle,
this.studio,
this.contentType:,
this.customData: const {},
this.isAuto,
this.position,
this.isLive,
this.dialogThumbnail,
this.notificationThumbnail,
}) :
...
private fun startCast(pCall: MethodCall, pResult: Result) {
val tArguments = pCall.arguments as? Map<String, Any>
…
// [1] メタデータの設定
val tMovieMetadata =
MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
tMovieMetadata.putString(MediaMetadata.KEY_TITLE, tTitle)
tMovieMetadata.addImage(WebImage(Uri.parse(tDialogUrl), tWidth,
tHeight))
tMovieMetadata.addImage(WebImage(Uri.parse(tNotificationUrl)))
…
// [2] メディア情報の作成
val tMediaInfo = MediaInfo.Builder(tUri)
.setStreamType(tStreamType)
.setContentType(tContentType)
.setMetadata(tMovieMetadata)
.setCustomData(tJsonObject)
.build() …
Android/Kotlin
// [3] 読み込み時のオプション
val tMediaLoadOptions: MediaLoadOptions =
MediaLoadOptions.Builder()
.setAutoplay(tIsAuto)
.setPlayPosition(tPlayPosition.toLong())
.build()
…
// [4] Chromecastにキャスト
val tIsSuccess = (tCastSession?.remoteMediaClient?.load(tMediaInfo,
tMediaLoadOptions) != null)
pResult.success(tIsSuccess)
}
private func startCast(_ pCall: FlutterMethodCall, _ pResult: @escaping
FlutterResult) {
let tArguments = pCall.arguments as? Dictionary<String, Any>
…
// [1] メタデータの設定
let tMetadata:GCKMediaMetadata = GCKMediaMetadata(metadataType:
GCKMediaMetadataType.movie)
tMetadata.setString(tTitle, forKey: kGCKMetadataKeyTitle)
tMetadata.addImage(GCKImage(url: URL(string: (tDialogUrl)), width:
tWidth, height: tHeight))
…
// [2] メディア情報の作成
let tBuilder = GCKMediaInformationBuilder.init(contentURL: tParseUrl)
tBuilder.contentID = tUri
tBuilder.streamType = tStreamType
tBuilder.contentType = tContentType
tBuilder.metadata = tMetadata
tBuilder.customData = tJsonData …
// [3] 読み込み時のオプション
let tMediaLoadOption = GCKMediaLoadOptions();
tMediaLoadOption.autoplay = tIsAuto
tMediaLoadOption.playPosition = tPlayPosition
…
// [4] Chromecastにキャスト
var tIsSuccess = true;
if (tCastSession?.remoteMediaClient?
.loadMedia(tMediaInfo, with: tMediaLoadOption)) != nil {
tIsSuccess = true;
} else {
tIsSuccess = false
}
pResult(tIsSuccess)
}
iOS/Swift
実演(動画)
● 仕様
○ Android / iOSで動作するFlutterプラグイン及びアプリ
■ Flutter : 1.2.1
■ Android : Kotlin 1.3.11 / gms-play-service : 16.1.2
■ iOS : Swift 4.2.1 / google-cast-sdk : 4.3.5
● 開発エディタ
○ VSCode
○ Android Studio
○ Xcode
● Google Cast Document
○ https://developers.google.com/cast/docs/developers
● Google Cast GitHub(Android/iOS)
○ https://github.com/googlecast/CastVideos-ios
○ https://github.com/googlecast/CastVideos-android
ここから先のスライドは補足
補足 iOS/Xcodeで開発する場合の注意
● Xcode10以上、iOS 12以降をターゲットにしている場合
● Access Wifi InformationをONにする
Cast Buttonの表示
● キャスト可能なデバイスがあるときはアイコンを表示、接続中は接続中のア
イコン、キャスト可能なデバイスがない場合は非表示に切り替える
● Cast StatusをAndroid/iOSのプラグイン側から取得
● Cast Statusの状態をもとにFlutter側でアイコンの表示・非表示を切り替える
キャストしてない キャスト中
private fun getCastState(pCall: MethodCall, pResult: Result) {
val tCastContext = CastContext.getSharedInstance()
val Status = tCastContext.castState
pResult.success(tStatus)
}
Android/Kotlin
enum CastStatus {
NO_DEVICES_AVAILABLE,
NOT_CONNECTED,
CONNECTING,
CONNECTED,
}
…
Future<CastStatus> getCastStatus() async {
CastStatus _castStatus;
int _result = await _channel.invokeMethod('GetCastStatus');
_castStatus = _getCastStateByValue(_result);
return _castStatus;
}
…
CastStatus _getCastStateByValue(int result) {
switch (result) {
case 1:
_castStatus = CastStatus.NO_DEVICES_AVAILABLE; …
break;
case 2:
_castStatus = CastStatus.NOT_CONNECTED; …
break;
…
chrome_cast_plugin.dart
private func getCastStatus(_ pCall: FlutterMethodCall, _ pResult: @escaping
FlutterResult) {
let tCastContext = GCKCastContext.sharedInstance()
let tStatus = tCastContext.castState.rawValue
pResult(tStatus)
}
iOS/Swift
リスナーの登録
● SessionManager Listener
○ アプリ全体のキャストセッションを管理
○ SessionManagerに登録する
■ アプリがセッションを開始したとき
■ アプリがセッションとの接続を停止したとき など…
● RemoteMediaClient Listener
○ アプリと現在キャストしているデバイス(Chromecast)のメディアのセッションを管理
○ CastSessionに登録する
■ 動画などをキャストをしたとき
■ キャストした動画に変更があったとき など…
● Flutter側の実装は必要なし
○ ※リスナーの中身の実装は今回は省略
class ChromeCastPlugin(private val pActivity: Activity, private val pChannel: MethodChannel):
…
CastContext.getSharedInstance()?.sessionManager?
.addSessionManagerListener(*fugafura, CastSession::class.java)
...
CastContext.getSharedInstance()?.sessionManager?.currentCastSession?
.remoteMediaClient?.registerCallback(*hogehoge)
….
Android/Kotlin
public class SwiftChromeCastPlugin: NSObject, FlutterPlugin, GCKSessionManagerListener, GCKRemoteMediaClientListener {
…
GCKCastContext.sharedInstance().sessionManager.add(*hogehoge)
….
GCKCastContext.sharedInstance().sessionManager.currentSession?.remoteMediaClient?.add(*fugafuga)
….
iOS/Swift
キャストした動画を制御する
● キャストしている動画をFlutter側から制御(一時停
止やシークなど)する
● Flutter側
○ 動画コントロールパネル用のUIを作る
● Plugin側
○ 現在のキャストセッションからリモートメディアクライアン
ト(remoteMediaClient)を取得して再生(play)や一時停止
(pause)、シーク(seek)を呼ぶ
シーク
停止
再生
ボリューム変更
Android/Kotlin
private fun setMediaStreamPosition(pCall: MethodCall, pResult: Result) {
…
tCastSession.remoteMediaClient.seek(tSeekTime.toLong())
…
}
iOS/Swift
private func setMediaStreamPosition(_ pCall: FlutterMethodCall, _ pResult:
@escaping FlutterResult) {
…
let tOptions = GCKMediaSeekOptions()
// remotMediaClient.seekに渡す引数のために、ミリ秒から秒へ変換
tOptions.interval = tTime! / 1000
tCastSession?.remoteMediaClient?.seek(with: tOptions)
}
chrome_cast_plugin.dart
Future<bool> setMediaStreamPosition(var milliseconds) async {
bool _result =
await _channel.invokeMethod('SetMediaStreamPosition',
milliseconds);
return _result;
}
例)シーク
応用 キャストボタンの表示バグをなくす
● Cast Statusの状態がNOT_CONNECTEDの状態からアプリがバックグラウンドに
いくと、キャストデバイスはない状態(NO_DEVICES_AVALLABLE)になる
● アプリがフォアグラウンドに戻った際に、キャストボタンが表示・非表示をし
て点滅する
○ Google Playムービーなどの一部アプリでも起きている
● アプリがフォアグラウンドに戻ってきた際、数ミリ秒まってからCast Stateの
状態を取得する
chrome_cast_plugin.dart
class ChromeCast extends WidgetsBindingObserver {
…
WidgetsBinding.instance.addObserver(this);
….
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
switch (state) {
case AppLifecycleState.paused:
_isDelay = true;
break;
default:
break;
}
}
…
abstract WidgetsBindingObserver
アプリのライフサイクルを監視する
didChangeAppLifecycleState関数を使うために継承
didChangeAppLifecycleState
AppLifecycleState state
● resumed
● inactive
● paused
● suspending
のいずれかの状態を返す
pausedのときに遅延を起こす
WidgetsBindingObserverに登録
chrome_cast_plugin.dart
...
typedef OnChangeCastState = Function(CastStatus);
…
class ChromeCast extends WidgetsBindingObserver {
static const MethodChannel _channel =
const MethodChannel('chrome_cast_plugin');
OnChangeCastState onChangeCastState;
bool _isDelay = false;
bool _isWaiting = false;
…
ChromeCast._internal() {
…
if (call.method == "OnChangeCastState") {
CastStatus _castStatus = _getCastStateByValue(call.arguments);
if (_isDelay) {
if (_isWaiting == false) {
_delayGetCastStatus();
}
} else {
onChangeCastState(_castStatus);
}
}
});
...
}
…
chrome_cast_plugin.dart
OnChangeCastState
キャストの状態が変化したときに呼ばれるようにデリ
ゲートを用意
_delayGetCastStatus
数ミリ秒待機してからキャストの状態を取得する(自作)
…
_delayGetCastStatus() async {
_isWaiting = true;
await Future.delayed(Duration(milliseconds: _waitSeconds));
getCastStatus().then((onStatus) {
if (onStatus != CastStatus.NO_DEVICES_AVAILABLE) {
onChangeCastState(onStatus);
_isDelay = false;
}
_isWaiting = false;
});
}
…
chrome_cast_plugin.dart
数秒待機してCastStatusを取得する

Weitere ähnliche Inhalte

Was ist angesagt?

セキュアエレメントとIotデバイスセキュリティ2
セキュアエレメントとIotデバイスセキュリティ2セキュアエレメントとIotデバイスセキュリティ2
セキュアエレメントとIotデバイスセキュリティ2Kentaro Mitsuyasu
 
Anthos を使ったエンタープライズ向けクラスタの設計とアップグレード戦略のススメ(CloudNative Days Tokyo 2021 発表資料)
Anthos を使ったエンタープライズ向けクラスタの設計とアップグレード戦略のススメ(CloudNative Days Tokyo 2021 発表資料)Anthos を使ったエンタープライズ向けクラスタの設計とアップグレード戦略のススメ(CloudNative Days Tokyo 2021 発表資料)
Anthos を使ったエンタープライズ向けクラスタの設計とアップグレード戦略のススメ(CloudNative Days Tokyo 2021 発表資料)NTT DATA Technology & Innovation
 
Yahoo!ニュースにおけるBFFパフォーマンスチューニング事例
Yahoo!ニュースにおけるBFFパフォーマンスチューニング事例Yahoo!ニュースにおけるBFFパフォーマンスチューニング事例
Yahoo!ニュースにおけるBFFパフォーマンスチューニング事例Yahoo!デベロッパーネットワーク
 
Usb接続するアプリを開発した時に試行錯誤した事
Usb接続するアプリを開発した時に試行錯誤した事Usb接続するアプリを開発した時に試行錯誤した事
Usb接続するアプリを開発した時に試行錯誤した事Masataka Kono
 
Try new transport protocol SRT (ver. 2)
Try new transport protocol SRT  (ver. 2)Try new transport protocol SRT  (ver. 2)
Try new transport protocol SRT (ver. 2)Tetsuyuki Kobayashi
 
CEDEC2019 大規模モバイルゲーム運用におけるマスタデータ管理事例
CEDEC2019 大規模モバイルゲーム運用におけるマスタデータ管理事例CEDEC2019 大規模モバイルゲーム運用におけるマスタデータ管理事例
CEDEC2019 大規模モバイルゲーム運用におけるマスタデータ管理事例sairoutine
 
Keycloak入門-OpenID ConnectによるAPIセキュリティ
Keycloak入門-OpenID ConnectによるAPIセキュリティKeycloak入門-OpenID ConnectによるAPIセキュリティ
Keycloak入門-OpenID ConnectによるAPIセキュリティYuichi Nakamura
 
マイクロサービスバックエンドAPIのためのRESTとgRPC
マイクロサービスバックエンドAPIのためのRESTとgRPCマイクロサービスバックエンドAPIのためのRESTとgRPC
マイクロサービスバックエンドAPIのためのRESTとgRPCdisc99_
 
○ヶ月でできた!?さくらのクラウド開発秘話(【ヒカ☆ラボ】さくらインターネットとMilkcocoa!年末イベント:ここだけのウラ話)
○ヶ月でできた!?さくらのクラウド開発秘話(【ヒカ☆ラボ】さくらインターネットとMilkcocoa!年末イベント:ここだけのウラ話)○ヶ月でできた!?さくらのクラウド開発秘話(【ヒカ☆ラボ】さくらインターネットとMilkcocoa!年末イベント:ここだけのウラ話)
○ヶ月でできた!?さくらのクラウド開発秘話(【ヒカ☆ラボ】さくらインターネットとMilkcocoa!年末イベント:ここだけのウラ話)さくらインターネット株式会社
 
エンジニアのためのOSSライセンス管理~OSS管理ツールの池の水全部抜く~
エンジニアのためのOSSライセンス管理~OSS管理ツールの池の水全部抜く~エンジニアのためのOSSライセンス管理~OSS管理ツールの池の水全部抜く~
エンジニアのためのOSSライセンス管理~OSS管理ツールの池の水全部抜く~Daisuke Morishita
 
アプリの鍵が消える時_Droid kaigi2018
アプリの鍵が消える時_Droid kaigi2018アプリの鍵が消える時_Droid kaigi2018
アプリの鍵が消える時_Droid kaigi2018ak_shio_555
 
Node RED で実現する製造業の DX
Node RED で実現する製造業の DXNode RED で実現する製造業の DX
Node RED で実現する製造業の DX雅治 新澤
 
大規模環境のOpenStack アップグレードの考え方と実施のコツ
大規模環境のOpenStackアップグレードの考え方と実施のコツ大規模環境のOpenStackアップグレードの考え方と実施のコツ
大規模環境のOpenStack アップグレードの考え方と実施のコツTomoya Hashimoto
 
Microsoft Partner Network ガイドライン
Microsoft Partner Network ガイドラインMicrosoft Partner Network ガイドライン
Microsoft Partner Network ガイドラインHiroyasu Sato
 
WebRTCのオーディオ処理の謎、誰か教えて!
WebRTCのオーディオ処理の謎、誰か教えて!WebRTCのオーディオ処理の謎、誰か教えて!
WebRTCのオーディオ処理の謎、誰か教えて!mganeko
 
AlmaLinux と Rocky Linux の誕生経緯&比較
AlmaLinux と Rocky Linux の誕生経緯&比較AlmaLinux と Rocky Linux の誕生経緯&比較
AlmaLinux と Rocky Linux の誕生経緯&比較beyond Co., Ltd.
 
実践!OpenTelemetry と OSS を使った Observability 基盤の構築(CloudNative Days Tokyo 2022 発...
実践!OpenTelemetry と OSS を使った Observability 基盤の構築(CloudNative Days Tokyo 2022 発...実践!OpenTelemetry と OSS を使った Observability 基盤の構築(CloudNative Days Tokyo 2022 発...
実践!OpenTelemetry と OSS を使った Observability 基盤の構築(CloudNative Days Tokyo 2022 発...NTT DATA Technology & Innovation
 
MQTTとAMQPと.NET
MQTTとAMQPと.NETMQTTとAMQPと.NET
MQTTとAMQPと.NETterurou
 

Was ist angesagt? (20)

セキュアエレメントとIotデバイスセキュリティ2
セキュアエレメントとIotデバイスセキュリティ2セキュアエレメントとIotデバイスセキュリティ2
セキュアエレメントとIotデバイスセキュリティ2
 
Anthos を使ったエンタープライズ向けクラスタの設計とアップグレード戦略のススメ(CloudNative Days Tokyo 2021 発表資料)
Anthos を使ったエンタープライズ向けクラスタの設計とアップグレード戦略のススメ(CloudNative Days Tokyo 2021 発表資料)Anthos を使ったエンタープライズ向けクラスタの設計とアップグレード戦略のススメ(CloudNative Days Tokyo 2021 発表資料)
Anthos を使ったエンタープライズ向けクラスタの設計とアップグレード戦略のススメ(CloudNative Days Tokyo 2021 発表資料)
 
Yahoo!ニュースにおけるBFFパフォーマンスチューニング事例
Yahoo!ニュースにおけるBFFパフォーマンスチューニング事例Yahoo!ニュースにおけるBFFパフォーマンスチューニング事例
Yahoo!ニュースにおけるBFFパフォーマンスチューニング事例
 
Usb接続するアプリを開発した時に試行錯誤した事
Usb接続するアプリを開発した時に試行錯誤した事Usb接続するアプリを開発した時に試行錯誤した事
Usb接続するアプリを開発した時に試行錯誤した事
 
Try new transport protocol SRT (ver. 2)
Try new transport protocol SRT  (ver. 2)Try new transport protocol SRT  (ver. 2)
Try new transport protocol SRT (ver. 2)
 
CEDEC2019 大規模モバイルゲーム運用におけるマスタデータ管理事例
CEDEC2019 大規模モバイルゲーム運用におけるマスタデータ管理事例CEDEC2019 大規模モバイルゲーム運用におけるマスタデータ管理事例
CEDEC2019 大規模モバイルゲーム運用におけるマスタデータ管理事例
 
Keycloak入門-OpenID ConnectによるAPIセキュリティ
Keycloak入門-OpenID ConnectによるAPIセキュリティKeycloak入門-OpenID ConnectによるAPIセキュリティ
Keycloak入門-OpenID ConnectによるAPIセキュリティ
 
マイクロサービスバックエンドAPIのためのRESTとgRPC
マイクロサービスバックエンドAPIのためのRESTとgRPCマイクロサービスバックエンドAPIのためのRESTとgRPC
マイクロサービスバックエンドAPIのためのRESTとgRPC
 
○ヶ月でできた!?さくらのクラウド開発秘話(【ヒカ☆ラボ】さくらインターネットとMilkcocoa!年末イベント:ここだけのウラ話)
○ヶ月でできた!?さくらのクラウド開発秘話(【ヒカ☆ラボ】さくらインターネットとMilkcocoa!年末イベント:ここだけのウラ話)○ヶ月でできた!?さくらのクラウド開発秘話(【ヒカ☆ラボ】さくらインターネットとMilkcocoa!年末イベント:ここだけのウラ話)
○ヶ月でできた!?さくらのクラウド開発秘話(【ヒカ☆ラボ】さくらインターネットとMilkcocoa!年末イベント:ここだけのウラ話)
 
エンジニアのためのOSSライセンス管理~OSS管理ツールの池の水全部抜く~
エンジニアのためのOSSライセンス管理~OSS管理ツールの池の水全部抜く~エンジニアのためのOSSライセンス管理~OSS管理ツールの池の水全部抜く~
エンジニアのためのOSSライセンス管理~OSS管理ツールの池の水全部抜く~
 
アプリの鍵が消える時_Droid kaigi2018
アプリの鍵が消える時_Droid kaigi2018アプリの鍵が消える時_Droid kaigi2018
アプリの鍵が消える時_Droid kaigi2018
 
Node RED で実現する製造業の DX
Node RED で実現する製造業の DXNode RED で実現する製造業の DX
Node RED で実現する製造業の DX
 
KeycloakでAPI認可に入門する
KeycloakでAPI認可に入門するKeycloakでAPI認可に入門する
KeycloakでAPI認可に入門する
 
大規模環境のOpenStack アップグレードの考え方と実施のコツ
大規模環境のOpenStackアップグレードの考え方と実施のコツ大規模環境のOpenStackアップグレードの考え方と実施のコツ
大規模環境のOpenStack アップグレードの考え方と実施のコツ
 
Microsoft Partner Network ガイドライン
Microsoft Partner Network ガイドラインMicrosoft Partner Network ガイドライン
Microsoft Partner Network ガイドライン
 
TLS, HTTP/2演習
TLS, HTTP/2演習TLS, HTTP/2演習
TLS, HTTP/2演習
 
WebRTCのオーディオ処理の謎、誰か教えて!
WebRTCのオーディオ処理の謎、誰か教えて!WebRTCのオーディオ処理の謎、誰か教えて!
WebRTCのオーディオ処理の謎、誰か教えて!
 
AlmaLinux と Rocky Linux の誕生経緯&比較
AlmaLinux と Rocky Linux の誕生経緯&比較AlmaLinux と Rocky Linux の誕生経緯&比較
AlmaLinux と Rocky Linux の誕生経緯&比較
 
実践!OpenTelemetry と OSS を使った Observability 基盤の構築(CloudNative Days Tokyo 2022 発...
実践!OpenTelemetry と OSS を使った Observability 基盤の構築(CloudNative Days Tokyo 2022 発...実践!OpenTelemetry と OSS を使った Observability 基盤の構築(CloudNative Days Tokyo 2022 発...
実践!OpenTelemetry と OSS を使った Observability 基盤の構築(CloudNative Days Tokyo 2022 発...
 
MQTTとAMQPと.NET
MQTTとAMQPと.NETMQTTとAMQPと.NET
MQTTとAMQPと.NET
 

Ähnlich wie Cast SDK for Flutter

QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会Jumpei Ogawa
 
Pf部2012年1月勉強会.androidsola
Pf部2012年1月勉強会.androidsolaPf部2012年1月勉強会.androidsola
Pf部2012年1月勉強会.androidsolaandroid sola
 
Titanium Mobile
Titanium MobileTitanium Mobile
Titanium MobileNaoya Ito
 
Windows azure mobile services による mobile + cloud アプリケーション超高速開発
Windows azure mobile services による mobile + cloud アプリケーション超高速開発Windows azure mobile services による mobile + cloud アプリケーション超高速開発
Windows azure mobile services による mobile + cloud アプリケーション超高速開発Shotaro Suzuki
 
Azure IoT Edge で Custom Vision
Azure IoT Edge で Custom VisionAzure IoT Edge で Custom Vision
Azure IoT Edge で Custom VisionYoshitaka Seo
 
Redmineosaka 20 talk_crosspoints
Redmineosaka 20 talk_crosspointsRedmineosaka 20 talk_crosspoints
Redmineosaka 20 talk_crosspointsShinji Tamura
 
WebRTCを始めよう! HTML5fun 第一回勉強会
WebRTCを始めよう! HTML5fun 第一回勉強会WebRTCを始めよう! HTML5fun 第一回勉強会
WebRTCを始めよう! HTML5fun 第一回勉強会Yusuke Naka
 
[AI08] 深層学習フレームワーク Chainer × Microsoft で広がる応用
[AI08] 深層学習フレームワーク Chainer × Microsoft で広がる応用[AI08] 深層学習フレームワーク Chainer × Microsoft で広がる応用
[AI08] 深層学習フレームワーク Chainer × Microsoft で広がる応用de:code 2017
 
"Up" with vagrant and docker
"Up" with vagrant and docker"Up" with vagrant and docker
"Up" with vagrant and dockerHiroshi Miura
 
UnityとnodeとMMDと
UnityとnodeとMMDとUnityとnodeとMMDと
UnityとnodeとMMDとsters
 
FlutterをRenderObjectまで理解する
FlutterをRenderObjectまで理解するFlutterをRenderObjectまで理解する
FlutterをRenderObjectまで理解するKeisukeKiriyama
 
VRのマルチプラットフォーム開発について
VRのマルチプラットフォーム開発についてVRのマルチプラットフォーム開発について
VRのマルチプラットフォーム開発についてyashinut
 
"Up" with vagrant and docker
"Up" with vagrant and docker"Up" with vagrant and docker
"Up" with vagrant and dockerHiroshi Miura
 
.NET Gadgeteer のハンズオン資料 (2014年3月版)
.NET Gadgeteer のハンズオン資料 (2014年3月版).NET Gadgeteer のハンズオン資料 (2014年3月版)
.NET Gadgeteer のハンズオン資料 (2014年3月版)Yoshitaka Seo
 
Unreal Engine でアプリ開発~ MRTK UXTools for Unreal V0.9.0 ~
Unreal Engine でアプリ開発~ MRTK UXTools for Unreal V0.9.0 ~Unreal Engine でアプリ開発~ MRTK UXTools for Unreal V0.9.0 ~
Unreal Engine でアプリ開発~ MRTK UXTools for Unreal V0.9.0 ~Takahiro Miyaura
 
Let's begin WebRTC
Let's begin WebRTCLet's begin WebRTC
Let's begin WebRTCyoshikawa_t
 
Grafana Dashboards as Code
Grafana Dashboards as CodeGrafana Dashboards as Code
Grafana Dashboards as CodeTakuhiro Yoshida
 

Ähnlich wie Cast SDK for Flutter (20)

QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
 
Pf部2012年1月勉強会.androidsola
Pf部2012年1月勉強会.androidsolaPf部2012年1月勉強会.androidsola
Pf部2012年1月勉強会.androidsola
 
Titanium Mobile
Titanium MobileTitanium Mobile
Titanium Mobile
 
Windows azure mobile services による mobile + cloud アプリケーション超高速開発
Windows azure mobile services による mobile + cloud アプリケーション超高速開発Windows azure mobile services による mobile + cloud アプリケーション超高速開発
Windows azure mobile services による mobile + cloud アプリケーション超高速開発
 
Azure IoT Edge で Custom Vision
Azure IoT Edge で Custom VisionAzure IoT Edge で Custom Vision
Azure IoT Edge で Custom Vision
 
Redmineosaka 20 talk_crosspoints
Redmineosaka 20 talk_crosspointsRedmineosaka 20 talk_crosspoints
Redmineosaka 20 talk_crosspoints
 
WebRTCを始めよう! HTML5fun 第一回勉強会
WebRTCを始めよう! HTML5fun 第一回勉強会WebRTCを始めよう! HTML5fun 第一回勉強会
WebRTCを始めよう! HTML5fun 第一回勉強会
 
[AI08] 深層学習フレームワーク Chainer × Microsoft で広がる応用
[AI08] 深層学習フレームワーク Chainer × Microsoft で広がる応用[AI08] 深層学習フレームワーク Chainer × Microsoft で広がる応用
[AI08] 深層学習フレームワーク Chainer × Microsoft で広がる応用
 
Android bluetooth
Android bluetoothAndroid bluetooth
Android bluetooth
 
"Up" with vagrant and docker
"Up" with vagrant and docker"Up" with vagrant and docker
"Up" with vagrant and docker
 
UnityとnodeとMMDと
UnityとnodeとMMDとUnityとnodeとMMDと
UnityとnodeとMMDと
 
FlutterをRenderObjectまで理解する
FlutterをRenderObjectまで理解するFlutterをRenderObjectまで理解する
FlutterをRenderObjectまで理解する
 
VRのマルチプラットフォーム開発について
VRのマルチプラットフォーム開発についてVRのマルチプラットフォーム開発について
VRのマルチプラットフォーム開発について
 
"Up" with vagrant and docker
"Up" with vagrant and docker"Up" with vagrant and docker
"Up" with vagrant and docker
 
MRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTER
MRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTERMRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTER
MRTK-Unreal(UX Tools) を利用した HoloLens 2 アプリ開発 | UNREAL FEST EXTREME 2020 WINTER
 
.NET Gadgeteer のハンズオン資料 (2014年3月版)
.NET Gadgeteer のハンズオン資料 (2014年3月版).NET Gadgeteer のハンズオン資料 (2014年3月版)
.NET Gadgeteer のハンズオン資料 (2014年3月版)
 
Unreal Engine でアプリ開発~ MRTK UXTools for Unreal V0.9.0 ~
Unreal Engine でアプリ開発~ MRTK UXTools for Unreal V0.9.0 ~Unreal Engine でアプリ開発~ MRTK UXTools for Unreal V0.9.0 ~
Unreal Engine でアプリ開発~ MRTK UXTools for Unreal V0.9.0 ~
 
Let's begin WebRTC
Let's begin WebRTCLet's begin WebRTC
Let's begin WebRTC
 
Grafana Dashboards as Code
Grafana Dashboards as CodeGrafana Dashboards as Code
Grafana Dashboards as Code
 
20120118 titanium
20120118 titanium20120118 titanium
20120118 titanium
 

Cast SDK for Flutter

  • 1. Cast SDK for Flutter
  • 2. 山田 幸司 koji.yamada@gree.net グリー株式会社 開発本部 / インフラストラクチャ部 / ディベロップメント オペレーションズグループ / サービスディベロップメント チーム 所属 業務内容 VRアプリフロントエンド担当 最近のお仕事はFlutter / Unity / Unreal Engine 4など
  • 3. アジェンダ ● Chromecastについて ○ Chromecastとは? ○ Cast SDKについて ● Cast SDK for Flutterを作る ○ Flutterでプラグインを作る準備やインストール方法など ○ 構成から実装のおおまかな流れ ○ 実装方法などについて
  • 4. Chromecast ● Googleが開発・販売する小型のデバイス ● HDMI端子に接続/Wi-Fiを介してスマートフォンやタブレットなどで再生して いる動画、写真、ウェブサイトなどをディスプレイに表示出来るデバイス ● ミラーリングとはやや違ってスマートフォンをコントローラとして扱い動画 の再生や停止などもできる ● 値段は5,000円程度(4K対応のUltraは10,000円程度) ● アプリで利用するにはCast SDKを組み込む必要がある ※↑アプリが対応しているかつ周辺にChromecast がある場合はこのアイコンが表示される
  • 5. Cast SDK ● Googleが提供するChromecast用のSDK ○ https://developers.google.com/cast/ ● Android/iOS/Chrome(ブラウザ)に対応 ○ Android → Java/Kotlin ○ iOS → Objective-C/Swift ○ Chrome → Javascript ● 公式のFlutter用Cast SDKはまだ存在しない ● Flutter版もいつか対応するかも ○ GitHubのissuesはあがっている ○ https://github.com/flutter/flutter/issues/18212
  • 6. Cast SDK for Flutter ● 今日の話 ○ Flutter用のChromecast Pluginを作る話 ○ ChromecastのAndroid/iOS両方に対応するアプリをさく っと作れる ● 実装するもの ○ 送信側(Sender)と受信側(Receiver)を作る必要があ りますが、今回はSenderアプリのみについて ○ 動画コントロールパネルやキャストボタンなどのUI部分 はFlutterで実装 ○ 動画をChromecastにキャスト/再生と停止ができる
  • 7. 準備 ● Plugin Packageプロジェクトの作成 ○ $ flutter create --template=plugin -i swift -a kotlin [プロジェクト名] 例) chrome_cast_plugin Android/build.gradle … dependencies { implementation 'com.google.android.gms:play-services-cast-framework:16.1.2' ... iOS/Podfile … target 'Runner' do pod 'google-cast-sdk', '~> 4.3' ... Sync Now pod install
  • 8. 構成 app main.dart lib chrome_cast_ plugin.dart Flutter/Dart Android/Kotlin iOS/Swift CastOptionsProvider.kt ChromeCastPlugin.kt SwiftChromeCast Plugin.swift Cast SDK for Android gms:play-services-cast Cast SDK for iOS google-cast-sdk ● main.dart ○ キャストボタンなどのUI表示 ● chrome_cast_plugin.dart ○ Kotlin/Swiftとのつなぎ プラグイン側
  • 9. Pluginで実装すること 1. Cast Contextの初期化 2. Cast Dialogの表示 3. Cast Buttonの表示 4. リスナーの登録 5. 動画をキャストする 6. キャストした動画を制御する
  • 10. Cast Contextの初期化 ● Chromecast の機能を使うために必要 ● Android ○ OptionsProviderインタフェースを実装したクラスの作成 ○ レシーバーIdのCastOptionsのインスタンスを作る ○ CastContext.getSharedInstance(Activity)で初期化 ● iOS ○ レシーバーIdのGCKCastOptionのインスタンスを作る ○ GCKCastContext.setSharedInstanceWith(GCKCastOption)で初期化 ● 初期化タイミング ○ pluginのregisterWith / register
  • 11. class ChromeCastPlugin(private val pActivity: Activity, private val pChannel: MethodChannel): … companion object { @JvmStatic fun registerWith(registrar: Registrar) { CastContext.getSharedInstance(pActivity) … Android/Kotlin static const MethodChannel _channel = const MethodChannel('chrome_cast_plugin'); chrome_cast_plugin.dart public class SwiftChromeCastPlugin: NSObject, FlutterPlugin { … public static func register(with registrar: FlutterPluginRegistrar) { let tCriteria = GCKDiscoveryCriteria(applicationID: [レシーバーのId]) let tOption = GCKCastOptions(discoveryCriteria: tCriteria) GCKCastContext.setSharedInstanceWith(tOption) … iOS/Swift
  • 12. class CastOptionsProvider : OptionsProvider { … override fun getCastOptions(context: Context): CastOptions? { val tAppId = context.resources.getIdentifier("chromecast_app_id", "string", context.packageName) … return CastOptions.Builder() .setReceiverApplicationId(context.getString(tAppId)) .build() } … } Android/Kotlin Cast Contextの初期化(Android CastOptionsProvider.kt) <?xml version="1.0" encoding="utf-8"?> <resources> <string name="chromecast_app_id">[レシーバーId]</string> </resources> app/res/values/string.xml
  • 13. Cast Dialogの表示 ● キャストしてないとき ○ キャスト可能なデバイス一覧を表示 ● キャストしてるとき ○ メディア情報、音量などを表示 ● Android(少々手間がかかる) ○ 現在キャスト中かどうかを判定して出し分けが必要 ○ 現在のキャストセッションがあれば MediaRouteControllerDialog ○ 無い場合はMediaRouteChooserDialogを呼ぶ ● iOS ○ presentCastDialogを呼ぶだけ
  • 14. private fun showDialog(pCall: MethodCall, pResult: Result) { val tCastSession = CastContext.getSharedInstance()?.sessionManager?.currentCastSession if (tCastSession != null) { val tControllerDialog = MediaRouteControllerDialog(pActivity, R.style.CastControllerDialogTheme) tControllerDialog.show() } else { val tChooserDialog = MediaRouteChooserDialog(pActivity, R.style.CastMediaRouterTheme) tChooserDialog.routeSelector = CastContext.getSharedInstance()?.mergedSelector!! tChooserDialog.show() } } Android/Kotlin Future<void> showCastDialog() async { await _channel.invokeMethod('ShowCastDialog'); } chrome_cast_plugin.dart private func showDialog(_ pCall: FlutterMethodCall, _ pResult: @escaping FlutterResult) { let tContext:GCKCastContext = GCKCastContext.sharedInstance() tContext.presentCastDialog() } iOS/Swift
  • 15. 動画をキャストする ● 動画をキャストするために必要な情報をFlutterからPlugin側に渡す ● 手順 ○ [1] キャストするための必要なメタデータ(Metadata)を作成するためのパ ラメータ ■ タイトル、キャストダイアログに出す画像など ○ [2] 作成したメタデータからメディア情報(MediaInfo)を作成するためのパ ラメータ ■ 動画のコンテンツタイプ(mp4など)、レシーバーに渡す独自のJsonデータなど ○ [3] 必要に応じて読み込み時のオプションを作成するためのパラメータ ■ 再生位置、自動再生するかどうかなど ○ [4] 動画をデバイスにキャスト
  • 16. Future<bool> startCast(Media media) async { bool _result = await _channel.invokeMethod('StartCast', { 'Uri': media.url, 'Title': media.title, 'Subtitle': media.subTitle, 'Studio': media.studio, 'ContentType': media.contentType, 'Images': { 'Dialog': { 'Url': media.dialogThumbnail.url, 'Width': media.dialogThumbnail.width, 'Height': media.dialogThumbnail.height }, 'Notification': { 'Url': media.notificationThumbnail.url, } }, chrome_cast_plugin.dart 'IsAuto': media.isAuto, 'PlayPosition': media.position, 'IsLive': media.isLive, 'CustomData': media.customData }); return _result; } ... class Media { Media({ this.url, this.title, this.subTitle, this.studio, this.contentType:, this.customData: const {}, this.isAuto, this.position, this.isLive, this.dialogThumbnail, this.notificationThumbnail, }) : ...
  • 17. private fun startCast(pCall: MethodCall, pResult: Result) { val tArguments = pCall.arguments as? Map<String, Any> … // [1] メタデータの設定 val tMovieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE) tMovieMetadata.putString(MediaMetadata.KEY_TITLE, tTitle) tMovieMetadata.addImage(WebImage(Uri.parse(tDialogUrl), tWidth, tHeight)) tMovieMetadata.addImage(WebImage(Uri.parse(tNotificationUrl))) … // [2] メディア情報の作成 val tMediaInfo = MediaInfo.Builder(tUri) .setStreamType(tStreamType) .setContentType(tContentType) .setMetadata(tMovieMetadata) .setCustomData(tJsonObject) .build() … Android/Kotlin // [3] 読み込み時のオプション val tMediaLoadOptions: MediaLoadOptions = MediaLoadOptions.Builder() .setAutoplay(tIsAuto) .setPlayPosition(tPlayPosition.toLong()) .build() … // [4] Chromecastにキャスト val tIsSuccess = (tCastSession?.remoteMediaClient?.load(tMediaInfo, tMediaLoadOptions) != null) pResult.success(tIsSuccess) }
  • 18. private func startCast(_ pCall: FlutterMethodCall, _ pResult: @escaping FlutterResult) { let tArguments = pCall.arguments as? Dictionary<String, Any> … // [1] メタデータの設定 let tMetadata:GCKMediaMetadata = GCKMediaMetadata(metadataType: GCKMediaMetadataType.movie) tMetadata.setString(tTitle, forKey: kGCKMetadataKeyTitle) tMetadata.addImage(GCKImage(url: URL(string: (tDialogUrl)), width: tWidth, height: tHeight)) … // [2] メディア情報の作成 let tBuilder = GCKMediaInformationBuilder.init(contentURL: tParseUrl) tBuilder.contentID = tUri tBuilder.streamType = tStreamType tBuilder.contentType = tContentType tBuilder.metadata = tMetadata tBuilder.customData = tJsonData … // [3] 読み込み時のオプション let tMediaLoadOption = GCKMediaLoadOptions(); tMediaLoadOption.autoplay = tIsAuto tMediaLoadOption.playPosition = tPlayPosition … // [4] Chromecastにキャスト var tIsSuccess = true; if (tCastSession?.remoteMediaClient? .loadMedia(tMediaInfo, with: tMediaLoadOption)) != nil { tIsSuccess = true; } else { tIsSuccess = false } pResult(tIsSuccess) } iOS/Swift
  • 20. ● 仕様 ○ Android / iOSで動作するFlutterプラグイン及びアプリ ■ Flutter : 1.2.1 ■ Android : Kotlin 1.3.11 / gms-play-service : 16.1.2 ■ iOS : Swift 4.2.1 / google-cast-sdk : 4.3.5 ● 開発エディタ ○ VSCode ○ Android Studio ○ Xcode ● Google Cast Document ○ https://developers.google.com/cast/docs/developers ● Google Cast GitHub(Android/iOS) ○ https://github.com/googlecast/CastVideos-ios ○ https://github.com/googlecast/CastVideos-android ここから先のスライドは補足
  • 21. 補足 iOS/Xcodeで開発する場合の注意 ● Xcode10以上、iOS 12以降をターゲットにしている場合 ● Access Wifi InformationをONにする
  • 22. Cast Buttonの表示 ● キャスト可能なデバイスがあるときはアイコンを表示、接続中は接続中のア イコン、キャスト可能なデバイスがない場合は非表示に切り替える ● Cast StatusをAndroid/iOSのプラグイン側から取得 ● Cast Statusの状態をもとにFlutter側でアイコンの表示・非表示を切り替える キャストしてない キャスト中
  • 23. private fun getCastState(pCall: MethodCall, pResult: Result) { val tCastContext = CastContext.getSharedInstance() val Status = tCastContext.castState pResult.success(tStatus) } Android/Kotlin enum CastStatus { NO_DEVICES_AVAILABLE, NOT_CONNECTED, CONNECTING, CONNECTED, } … Future<CastStatus> getCastStatus() async { CastStatus _castStatus; int _result = await _channel.invokeMethod('GetCastStatus'); _castStatus = _getCastStateByValue(_result); return _castStatus; } … CastStatus _getCastStateByValue(int result) { switch (result) { case 1: _castStatus = CastStatus.NO_DEVICES_AVAILABLE; … break; case 2: _castStatus = CastStatus.NOT_CONNECTED; … break; … chrome_cast_plugin.dart private func getCastStatus(_ pCall: FlutterMethodCall, _ pResult: @escaping FlutterResult) { let tCastContext = GCKCastContext.sharedInstance() let tStatus = tCastContext.castState.rawValue pResult(tStatus) } iOS/Swift
  • 24. リスナーの登録 ● SessionManager Listener ○ アプリ全体のキャストセッションを管理 ○ SessionManagerに登録する ■ アプリがセッションを開始したとき ■ アプリがセッションとの接続を停止したとき など… ● RemoteMediaClient Listener ○ アプリと現在キャストしているデバイス(Chromecast)のメディアのセッションを管理 ○ CastSessionに登録する ■ 動画などをキャストをしたとき ■ キャストした動画に変更があったとき など… ● Flutter側の実装は必要なし ○ ※リスナーの中身の実装は今回は省略
  • 25. class ChromeCastPlugin(private val pActivity: Activity, private val pChannel: MethodChannel): … CastContext.getSharedInstance()?.sessionManager? .addSessionManagerListener(*fugafura, CastSession::class.java) ... CastContext.getSharedInstance()?.sessionManager?.currentCastSession? .remoteMediaClient?.registerCallback(*hogehoge) …. Android/Kotlin public class SwiftChromeCastPlugin: NSObject, FlutterPlugin, GCKSessionManagerListener, GCKRemoteMediaClientListener { … GCKCastContext.sharedInstance().sessionManager.add(*hogehoge) …. GCKCastContext.sharedInstance().sessionManager.currentSession?.remoteMediaClient?.add(*fugafuga) …. iOS/Swift
  • 26. キャストした動画を制御する ● キャストしている動画をFlutter側から制御(一時停 止やシークなど)する ● Flutter側 ○ 動画コントロールパネル用のUIを作る ● Plugin側 ○ 現在のキャストセッションからリモートメディアクライアン ト(remoteMediaClient)を取得して再生(play)や一時停止 (pause)、シーク(seek)を呼ぶ シーク 停止 再生 ボリューム変更
  • 27. Android/Kotlin private fun setMediaStreamPosition(pCall: MethodCall, pResult: Result) { … tCastSession.remoteMediaClient.seek(tSeekTime.toLong()) … } iOS/Swift private func setMediaStreamPosition(_ pCall: FlutterMethodCall, _ pResult: @escaping FlutterResult) { … let tOptions = GCKMediaSeekOptions() // remotMediaClient.seekに渡す引数のために、ミリ秒から秒へ変換 tOptions.interval = tTime! / 1000 tCastSession?.remoteMediaClient?.seek(with: tOptions) } chrome_cast_plugin.dart Future<bool> setMediaStreamPosition(var milliseconds) async { bool _result = await _channel.invokeMethod('SetMediaStreamPosition', milliseconds); return _result; } 例)シーク
  • 28. 応用 キャストボタンの表示バグをなくす ● Cast Statusの状態がNOT_CONNECTEDの状態からアプリがバックグラウンドに いくと、キャストデバイスはない状態(NO_DEVICES_AVALLABLE)になる ● アプリがフォアグラウンドに戻った際に、キャストボタンが表示・非表示をし て点滅する ○ Google Playムービーなどの一部アプリでも起きている ● アプリがフォアグラウンドに戻ってきた際、数ミリ秒まってからCast Stateの 状態を取得する
  • 29. chrome_cast_plugin.dart class ChromeCast extends WidgetsBindingObserver { … WidgetsBinding.instance.addObserver(this); …. @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); switch (state) { case AppLifecycleState.paused: _isDelay = true; break; default: break; } } … abstract WidgetsBindingObserver アプリのライフサイクルを監視する didChangeAppLifecycleState関数を使うために継承 didChangeAppLifecycleState AppLifecycleState state ● resumed ● inactive ● paused ● suspending のいずれかの状態を返す pausedのときに遅延を起こす WidgetsBindingObserverに登録
  • 30. chrome_cast_plugin.dart ... typedef OnChangeCastState = Function(CastStatus); … class ChromeCast extends WidgetsBindingObserver { static const MethodChannel _channel = const MethodChannel('chrome_cast_plugin'); OnChangeCastState onChangeCastState; bool _isDelay = false; bool _isWaiting = false; … ChromeCast._internal() { … if (call.method == "OnChangeCastState") { CastStatus _castStatus = _getCastStateByValue(call.arguments); if (_isDelay) { if (_isWaiting == false) { _delayGetCastStatus(); } } else { onChangeCastState(_castStatus); } } }); ... } … chrome_cast_plugin.dart OnChangeCastState キャストの状態が変化したときに呼ばれるようにデリ ゲートを用意 _delayGetCastStatus 数ミリ秒待機してからキャストの状態を取得する(自作)
  • 31. … _delayGetCastStatus() async { _isWaiting = true; await Future.delayed(Duration(milliseconds: _waitSeconds)); getCastStatus().then((onStatus) { if (onStatus != CastStatus.NO_DEVICES_AVAILABLE) { onChangeCastState(onStatus); _isDelay = false; } _isWaiting = false; }); } … chrome_cast_plugin.dart 数秒待機してCastStatusを取得する