SlideShare a Scribd company logo
1 of 31
Download to read offline
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を取得する

More Related Content

What's hot

Windowsフォームで大丈夫か?一番良いのを頼む。
Windowsフォームで大丈夫か?一番良いのを頼む。Windowsフォームで大丈夫か?一番良いのを頼む。
Windowsフォームで大丈夫か?一番良いのを頼む。Yuya Yamaki
 
async/await のしくみ
async/await のしくみasync/await のしくみ
async/await のしくみ信之 岩永
 
eStargzイメージとlazy pullingによる高速なコンテナ起動
eStargzイメージとlazy pullingによる高速なコンテナ起動eStargzイメージとlazy pullingによる高速なコンテナ起動
eStargzイメージとlazy pullingによる高速なコンテナ起動Kohei Tokunaga
 
【Unity】 Behavior TreeでAIを作る
 【Unity】 Behavior TreeでAIを作る 【Unity】 Behavior TreeでAIを作る
【Unity】 Behavior TreeでAIを作るtorisoup
 
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭するCEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭するYoshifumi Kawai
 
Googleのオープンなビーコン規格「Eddystone」とはなんなのか?
Googleのオープンなビーコン規格「Eddystone」とはなんなのか?Googleのオープンなビーコン規格「Eddystone」とはなんなのか?
Googleのオープンなビーコン規格「Eddystone」とはなんなのか?Fumihiko Sato
 
MagicOnion入門
MagicOnion入門MagicOnion入門
MagicOnion入門torisoup
 
第1回ROS勉強会発表資料 ROS+Gazeboではじめるロボットシミュレーション
第1回ROS勉強会発表資料 ROS+Gazeboではじめるロボットシミュレーション第1回ROS勉強会発表資料 ROS+Gazeboではじめるロボットシミュレーション
第1回ROS勉強会発表資料 ROS+Gazeboではじめるロボットシミュレーションakio19937
 
次世代ゲームにおける自動生成技術
次世代ゲームにおける自動生成技術 次世代ゲームにおける自動生成技術
次世代ゲームにおける自動生成技術 Youichiro Miyake
 
Unityネイティブプラグインマニアクス #denatechcon
Unityネイティブプラグインマニアクス #denatechconUnityネイティブプラグインマニアクス #denatechcon
Unityネイティブプラグインマニアクス #denatechconDeNA
 
ブラウザ自動化ツール カオスマップ風 - STAC2018 LT
ブラウザ自動化ツール カオスマップ風 - STAC2018 LTブラウザ自動化ツール カオスマップ風 - STAC2018 LT
ブラウザ自動化ツール カオスマップ風 - STAC2018 LThnisiji
 
Scapyで作る・解析するパケット
Scapyで作る・解析するパケットScapyで作る・解析するパケット
Scapyで作る・解析するパケットTakaaki Hoyo
 
Python製BDDツールで自動化してみた
Python製BDDツールで自動化してみたPython製BDDツールで自動化してみた
Python製BDDツールで自動化してみたKeijiUehata1
 
WebRTCの技術解説 第二版 公開版 完全版
WebRTCの技術解説 第二版 公開版 完全版WebRTCの技術解説 第二版 公開版 完全版
WebRTCの技術解説 第二版 公開版 完全版Contest Ntt-west
 
こわくない Git
こわくない Gitこわくない Git
こわくない GitKota Saito
 
ROS の活用による屋外の歩行者空間に適応した自律移動ロボットの開発
ROS の活用による屋外の歩行者空間に適応した自律移動ロボットの開発ROS の活用による屋外の歩行者空間に適応した自律移動ロボットの開発
ROS の活用による屋外の歩行者空間に適応した自律移動ロボットの開発Yoshitaka HARA
 
BuildKitの概要と最近の機能
BuildKitの概要と最近の機能BuildKitの概要と最近の機能
BuildKitの概要と最近の機能Kohei Tokunaga
 
グラフ構造のデータモデルをPower BIで可視化してみた
グラフ構造のデータモデルをPower BIで可視化してみたグラフ構造のデータモデルをPower BIで可視化してみた
グラフ構造のデータモデルをPower BIで可視化してみたCData Software Japan
 
T119_5年間の試行錯誤で進化したMVPVMパターン
T119_5年間の試行錯誤で進化したMVPVMパターンT119_5年間の試行錯誤で進化したMVPVMパターン
T119_5年間の試行錯誤で進化したMVPVMパターン伸男 伊藤
 

What's hot (20)

Windowsフォームで大丈夫か?一番良いのを頼む。
Windowsフォームで大丈夫か?一番良いのを頼む。Windowsフォームで大丈夫か?一番良いのを頼む。
Windowsフォームで大丈夫か?一番良いのを頼む。
 
async/await のしくみ
async/await のしくみasync/await のしくみ
async/await のしくみ
 
eStargzイメージとlazy pullingによる高速なコンテナ起動
eStargzイメージとlazy pullingによる高速なコンテナ起動eStargzイメージとlazy pullingによる高速なコンテナ起動
eStargzイメージとlazy pullingによる高速なコンテナ起動
 
【Unity】 Behavior TreeでAIを作る
 【Unity】 Behavior TreeでAIを作る 【Unity】 Behavior TreeでAIを作る
【Unity】 Behavior TreeでAIを作る
 
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭するCEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
 
Googleのオープンなビーコン規格「Eddystone」とはなんなのか?
Googleのオープンなビーコン規格「Eddystone」とはなんなのか?Googleのオープンなビーコン規格「Eddystone」とはなんなのか?
Googleのオープンなビーコン規格「Eddystone」とはなんなのか?
 
MagicOnion入門
MagicOnion入門MagicOnion入門
MagicOnion入門
 
第1回ROS勉強会発表資料 ROS+Gazeboではじめるロボットシミュレーション
第1回ROS勉強会発表資料 ROS+Gazeboではじめるロボットシミュレーション第1回ROS勉強会発表資料 ROS+Gazeboではじめるロボットシミュレーション
第1回ROS勉強会発表資料 ROS+Gazeboではじめるロボットシミュレーション
 
次世代ゲームにおける自動生成技術
次世代ゲームにおける自動生成技術 次世代ゲームにおける自動生成技術
次世代ゲームにおける自動生成技術
 
Unityネイティブプラグインマニアクス #denatechcon
Unityネイティブプラグインマニアクス #denatechconUnityネイティブプラグインマニアクス #denatechcon
Unityネイティブプラグインマニアクス #denatechcon
 
ブラウザ自動化ツール カオスマップ風 - STAC2018 LT
ブラウザ自動化ツール カオスマップ風 - STAC2018 LTブラウザ自動化ツール カオスマップ風 - STAC2018 LT
ブラウザ自動化ツール カオスマップ風 - STAC2018 LT
 
Scapyで作る・解析するパケット
Scapyで作る・解析するパケットScapyで作る・解析するパケット
Scapyで作る・解析するパケット
 
Python製BDDツールで自動化してみた
Python製BDDツールで自動化してみたPython製BDDツールで自動化してみた
Python製BDDツールで自動化してみた
 
UnityとROSの連携について
UnityとROSの連携についてUnityとROSの連携について
UnityとROSの連携について
 
WebRTCの技術解説 第二版 公開版 完全版
WebRTCの技術解説 第二版 公開版 完全版WebRTCの技術解説 第二版 公開版 完全版
WebRTCの技術解説 第二版 公開版 完全版
 
こわくない Git
こわくない Gitこわくない Git
こわくない Git
 
ROS の活用による屋外の歩行者空間に適応した自律移動ロボットの開発
ROS の活用による屋外の歩行者空間に適応した自律移動ロボットの開発ROS の活用による屋外の歩行者空間に適応した自律移動ロボットの開発
ROS の活用による屋外の歩行者空間に適応した自律移動ロボットの開発
 
BuildKitの概要と最近の機能
BuildKitの概要と最近の機能BuildKitの概要と最近の機能
BuildKitの概要と最近の機能
 
グラフ構造のデータモデルをPower BIで可視化してみた
グラフ構造のデータモデルをPower BIで可視化してみたグラフ構造のデータモデルをPower BIで可視化してみた
グラフ構造のデータモデルをPower BIで可視化してみた
 
T119_5年間の試行錯誤で進化したMVPVMパターン
T119_5年間の試行錯誤で進化したMVPVMパターンT119_5年間の試行錯誤で進化したMVPVMパターン
T119_5年間の試行錯誤で進化したMVPVMパターン
 

Similar to 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
 

Similar to 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を取得する