Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
動画プレイヤーはなぜカクつくのか 〜Flutterのレンダリングとネイティブの境界を解剖する〜
Search
Naruo Yoshiki
October 29, 2026
Programming
74
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
動画プレイヤーはなぜカクつくのか 〜Flutterのレンダリングとネイティブの境界を解剖する〜
FlutterKaigi2026 Session
Naruo Yoshiki
October 29, 2026
Other Decks in Programming
See All in Programming
[JA] Domain Modeling with Vanilla Rails: 「操作」から再考するRailsの設計論
yasaichi
4
970
パスワード再設定メールのURLについて知っておこう
uyuki234
0
190
全ての永続トークンを、生まれる前に消し去りたい!
sylph01
2
1.3k
AmebaブログはKMPから卒業します
kmars0605
0
240
作って学ぶSpring BootのAuto Configuration / JJUG Night Seminar 2026-10-19
rshindo
0
210
Performance Optimization With LLMs
xleitix
0
210
見えないものを探る要求要件定義に必要な基本的思考 / invisible-requirement-thinking
minodriven
15
8.8k
ChromeでローカルLLM
ryu1013
0
160
[2026-10-09]一枚の布で作らなくてもいい。 信頼性は「パッチワーク」で紡がれる~既存の運用と新しい可能性をつなぎ合わせてきたCREの話~
tosite
0
110
우리의 Nitro Modules는 당신의 Turbo Modules보다 아름답다
l2hyunwoo
0
220
Herb in Rails 8.2: Your ERB views, now HTML-aware @ Kaigi on Rails 2026, Tokyo, Japan
marcoroth
2
340
自作Discord音声botをLambda MicroVMsに移行した話 / Migrating My Discord Voice Bot to Lambda MicroVMs
akihisaikeda
0
150
Featured
See All Featured
Collaborative Software Design: How to facilitate domain modelling decisions
baasie
1
350
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
1
580
A Tale of Four Properties
chriscoyier
163
24k
Agile that works and the tools we love
rasmusluckow
331
22k
My Coaching Mixtape
mlcsv
0
360
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
3
1.4k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
Designing Powerful Visuals for Engaging Learning
tmiket
1
620
Why Mistakes Are the Best Teachers: Turning Failure into a Pathway for Growth
auna
0
330
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
310
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.9k
The Illustrated Children's Guide to Kubernetes
chrisshort
51
53k
Transcript
FlutterKaigi 2026 動画プレイヤーは なぜカクつくのか Flutterのレンダリングと ネイティブの境界を解剖する Day 1 10.29 THU
14:10–14:40 Yoshiki Naruo
株式会社サイバーエージェント / FANBASE事業部 Flutterのレンダリングと ネイティブの境界を解剖する 成尾 嘉貴 NARUO YOSHIKI 職種:
バックエンド&アプリエンジニア 技術: Flutter / Swift / Kotlin / Go / GCP Day 1 10.29 THU 14:10–14:40 経歴: サイバーエージェント2024年度新卒入社 X: @naruogram Yoshiki Naruo
None
突然ですが 4
動画を使ったアプリを開発 したことある人いますか🙋 5
苦労した経験ありますよね😭 6
動画アプリは Player実装以外にも 7
動画アプリは Player実装以外にも ・シークバーの実装 ・Playerに重なるUI ・コメント機能 ・ほかにも 8
動画アプリは Player実装以外にも ・シークバーの実装 モリモリになって ・Playerに重なるUI UIが重たくなる😭 ・コメント機能 ・ほかにも 9
動画を再生すると重い現象 映像が、ちょっと止まる 再生中の動画 コントロール 操作が、もっさりしている 10
今日、持ち帰ってほしいこと • 描画経路から、カクつく原因を切り分ける考え方 • 計測結果から、試す対策手法 11
今日の流れ 第1章 Flutterの描画とフレーム落ち 第2章 ネイティブ映像の表示経路 第3章 TextureとPlatformViewの使い分け 第4章 カクつきの計測と切り分け 第5章
原因別の対策と検証 12
第1章 Flutterの描画と フレーム落ち 13
再生位置の表示を更新する 動画 動画 再生位置やボタンの表示がカクつく まずは、どこで遅れるのかを見ていきます 00:10 00:11 Flutterで描く、この文字の変化を追う 14
再生位置の表示を更新する 動画 調べる場所を絞れるように UIの更新から画面表示までを追ってみます 動画 00:10 00:11 Flutterで描く、この文字の変化を追う 15
再生位置の表示を更新する 動画 動画 00:10 00:11 Flutterで描く、この文字の変化を追う 16
Build:表示内容の構成を更新する Build Widgetの構成 再生位置の状態 11秒 Text("00:11") 次の表示に必要なWidgetの構成 17
Layout:位置とサイズを決める Build Layout Widgetの構成 位置・サイズ 親からの制約の範囲 00:11 RenderObject 配置と描画を担当する Flutterのオブジェクト
18
Paint:描画命令を記録する Build Layout Paint Widgetの構成 位置・サイズ 描画命令 文字・座標・色などの 描画命令 00:11
この時点では、まだ表示前 19
ここまでが、UI側の処理 Build Layout Paint Widgetの構成 位置・サイズ 描画命令 UI Dartでフレームの内容を準備する処理 20
ここまでが、UI側の処理 Build Layout Paint 位置・サイズ 描画命令 ここまでで、描画する内容ができました 次は、Engineに渡してGPUで描くところを 説明します UI
Widgetの構成 Dartでフレームの内容を準備する処理 21
Scene:描画と合成の情報を渡す Dart側 UI Scene Flutter Engine 描画命令と、重なりなどの 合成情報をまとめたデータ FlutterView.render(scene) 22
Raster:描画を進めるCPU側の処理 Flutter Engine Scene UI Raster 構成・配置・描画命令 GPUへ描画を指示 Sceneを受け取り、 GPUへ渡す描画処理を組み立てる
23
GPU:描画命令を実行する Rasterスレッド Scene UI 構成・配置・描画命令 描画命令 Impeller(レンダラー) GPU向けの描画命令を生成 GPU 描画を実行
RasterスレッドがImpellerを使い、 Metal / Vulkanなどを通じて GPUへ描画を指示する CPU側 GPU側 24
OS:描画結果を合成して表示する Scene 描画命令 描画結果 UI Raster GPU OS 構成・配置・描画命令 GPUへ描画を指示
描画を実行 合成して表示 アプリなどの描画結果を合成し、 ディスプレイへの表示につなぐ 描画の完了と、実際の表示は別の時点 25
Flutter UIが画面に届くまでの流れ Dart Flutter Engine Scene ハードウェア 描画命令 プラットフォーム 描画結果
UI Raster GPU OS 構成・配置・描画命令 GPUへ描画を指示 描画を実行 合成して表示 準備する 指示する 描く 表示する 26
Flutter UIが画面に届くまで Dart Flutter Engine Scene ハードウェア 描画命令 プラットフォーム 描画結果
UI Raster GPU OS 構成・配置・描画命令 GPUへ描画を指示 描画を実行 合成して表示 準備する 指示する 描く 表示する これでFlutter UIの描画経路を整理できました 27
Flutter UIが画面に届くまで Dart Flutter Engine Scene ハードウェア 描画命令 プラットフォーム 描画結果
UI Raster GPU OS 準備する 指示する 描く 表示する ここで、シェーダーの準備に伴う遅れを 構成・配置・描画命令 GPUへ描画を指示 描画を実行 合成して表示 減らす仕組み、Impellerを見てみます 28
Impeller:シェーダーの事前準備 RasterはImpellerを使って、GPUへの描画を進める UI Raster GPU OS Build / Layout /
Paint Impeller 描画を実行 合成・表示 シェーダー:GPUで使う描画プログラム Engineのビルド時にシェーダーを準備済み 実行時のコンパイルに伴う遅れを減らす 29
Flutter UIが画面に届くまで Dart Flutter Engine Scene ハードウェア 描画命令 プラットフォーム 描画結果
UI Raster GPU OS 準備する 指示する 描く 表示する ここまでで、描画の流れが分かりました 構成・配置・描画命令 GPUへ描画を指示 描画を実行 合成して表示 次は、表示に間に合わない場合を見てみます これでFlutter UIの描画経路を整理できました 30
画面の更新タイミング(固定60 Hzの例) 0 ms 16.67 ms 33.33 ms 60 Hz=画面を1秒に60回更新
固定60 Hzなら、約16.67 msごとに更新 実際のHzは、端末・設定・動作状況で変わる 31
次の表示までに、描画結果を用意する 0 ms 16.67 ms 描画 Frame Bを描画 表示 Frame
A 33.33 ms Frame B 次の表示機会にBへ更新 固定60 Hzの概念図 32
表示に間に合わないと、前の画面が残る 0 ms 描画 表示 16.67 ms 33.33 ms Frame
Bを描画 Frame A Frame Aを継続 Bが間に合わず、Aが残る 固定60 Hzの概念図 33
表示に間に合わないと、前の画面が残る 0 ms 描画 16.67 ms 33.33 ms Frame Bを描画
表示が間に合わず、カクつきが発生 表示 Frame A Frame Aを継続 Bが間に合わず、Aが残る 固定60 Hzの概念図 34
表示に間に合わないと、前の画面が残る 0 ms 描画 16.67 ms 33.33 ms Frame Bを描画
同じ映像フレームが続いても 処理落ちとは限りません 映像のfpsと画面のHzを比べてみます 表示 Frame A Frame Aを継続 Bが間に合わず、Aが残る 固定60 Hzの概念図 35
30 fpsの映像は、60 Hzの画面で同じコマを2回ずつ表示する 例:30 fpsの映像を、固定60 Hzで表示 A・B・Cは映像の各コマ(図は100 ms分) 映像 30
fps A B C fps(フレーム/秒) 映像のコマ数を表す 1秒に30コマ 画面 60 Hz A A B B C C Hz(ヘルツ) 画面の更新回数を表す 1秒に60回更新 同じコマを2回ずつ表示して正常。処理落ちではない ※画面のHzは、端末や設定・動作状況で変わる 36
30 fpsの映像は、60 Hzの画面で同じコマを2回ずつ表示する 例:30 fpsの映像を、固定60 Hzで表示 A・B・Cは映像の各コマ(図は100 ms分) 映像 30
fps A B C この例のコマの繰り返しは正常です fps(フレーム/秒) 映像のコマ数を表す 次は、処理の遅れを調べるために 1秒に30コマ UIとRasterの動きを追ってみましょう 画面 60 Hz A A B B C C Hz(ヘルツ) 画面の更新回数を表す 1秒に60回更新 同じコマを2回ずつ表示して正常。処理落ちではない ※画面のHzは、端末や設定・動作状況で変わる 37
まず、UIがFrame 1の内容を準備する 時間 Frame 1=Flutter UIの1回分の描画 周期 1 UI 周期
2 周期 3 Frame 1 まず、Frame 1のBuild・Layout・Paint 38
UIが生成したSceneを、Rasterへ渡す 時間 同じFrame 1の処理を追う(処理時間は模式的) 周期 1 UI Raster 周期 2
周期 3 Frame 1 Frame 1 Frame 1のSceneから、GPUへの描画を準備する 39
次のUI処理と、前のRaster処理は並行できる 時間 連続更新中・別スレッドで実行する構成 UI Raster 周期 1 周期 2 周期
3 Frame 1 Frame 2 Frame 3 Frame 1 Frame 2 Frame 1のRaster処理中に、Frame 2のUI処理 40
並行に処理しても、表示に間に合わないことがある 表示に間に合わず、前の画面が残る 動きの更新間隔が乱れ、カクついて見える UI Raster GPU OS 構成・配置・描画命令 GPUへ描画を指示 描画を実行
合成して表示 UI / Rasterの処理が長引く GPU処理・同期の待機 OSの合成・表示の遅れ 41
第2章 ネイティブ映像の 表示経路 42
映像はネイティブが出力し、UIはFlutterが描画 プレイヤー画面には、映像とFlutter製のコントロールが重なっています 映像フレーム ネイティブプレイヤーが出力 映像は動くのに、操作がカクつく 動画 操作はできるのに、映像だけが止まる 00:11 再生位置・操作ボタン Flutterが描画
44
映像はネイティブが出力し、UIはFlutterが描画 プレイヤー画面には、映像とFlutter製のコントロールが重なっています 映像フレーム ネイティブプレイヤーが出力 UIと映像では、表示までの処理が違います 動画 まずは、映像が表示されるまでを見ていきます 00:11 再生位置・操作ボタン Flutterが描画
45
映像はネイティブが出力し、UIはFlutterが描画 プレイヤー画面には、映像とFlutter製のコントロールが重なっています 映像フレーム ネイティブプレイヤーが出力 動画 00:11 再生位置・操作ボタン Flutterが描画 46
ネイティブ側で、圧縮映像をデコードする 映像フレーム:動画を構成する1枚の画像 圧縮映像 デコード 映像フレーム 圧縮データから 画像を復元する 映像バッファ: デコード後の映像フレームの 画素データを保持する領域
47
ネイティブ側で、圧縮映像をデコードする 映像フレーム:動画を構成する1枚の画像 映像フレーム デコード デコードした映像を Flutterの画面に表示するには? 圧縮データから 圧縮映像 画像を復元する 映像バッファ:
デコード後の映像フレームの 画素データを保持する領域 48
映像をFlutterの画面に表示する方法は2つ 49
映像をFlutterの画面に表示する方法は2つ ①Texture ネイティブ側の画像を、Flutterの画面に表示するWidget 50
映像をFlutterの画面に表示する方法は2つ ①Texture ネイティブ側の画像を、Flutterの画面に表示するWidget ②PlatformView ネイティブのView(OSのUI部品)を、Flutterの画面に組み込む仕組み 51
Texture:映像をFlutterの描画に取り込む Flutter UIの描画情報と映像フレームを、Rasterが描画に使います Flutter Engine 描画内容 Flutter UI 表示する内容を指定 Scene
映像の位置・サイズ Raster 映像バッファ ネイティブ側 Impellerで描画を準備 映像フレーム Texture GPU・OSへ続く 映像フレームの更新自体には、通常Dartの実行は不要 52
PlatformView:映像を表示するViewを組み込む 映像を表示するOSのUI部品を、Flutterの画面内に配置します ネイティブView Flutterの画面 映像の表示 ネイティブView ネイティブView:OSのUI部品(UIView / View) 53
映像とUIは、別々の経路で更新される Textureの場合:映像フレームはRasterへ、再生位置などの値はFlutter UIの更新へ Scene 描画命令 描画結果 UI Raster GPU OS
Build / Layout / Paint Impeller 描画を実行 合成・表示 状態の値 映像フレーム 再生位置など 映像 ネイティブ Texture プレイヤー 外部映像を取り込む 映像フレームごとの Buildは不要 54
第3章 TextureとPlatformViewの 使い分け 55
映像をFlutterの画面に表示する方法は2つ Texture:映像フレームを Flutterの描画に取り込む TextureとPlatformView どちらを選ぶか、 必要な機能と描画負荷から考えます PlatformView:映像を表示するViewを Flutterの画面に組み込む 56
まず、必要な機能を確認する 必要な機能 画面の表現 実機で比較 必要な再生機能 UIの重ね合わせ・操作 UI・映像の計測 必要な再生機能とUIを実現できる構成で、性能を比較する 57
TextureとPlatformViewは、何を取り込むかが違う 比較すること Texture PlatformView 取り込むもの 動画の各コマ (映像フレーム) iOS / Android側の
映像表示部品 表示方法 Flutterが映像を描画する 映像表示部品を Flutterの画面に配置する ボタンやコメントは、どちらでもFlutterで作れる 58
video_playerでも、映像の表示方法を選べる video_player:Flutterの動画再生プラグイン video_player Texture PlatformView 59
video_playerでも、映像の表示方法を選べる video_player:Flutterの動画再生プラグイン video_player 次は、AndroidのPlatformViewです 映像を表示するViewとFlutter UIの 合成方式によって、性能が変わります Texture PlatformView 60
Android:Hybrid Composition View階層:Androidの画面部品を配置・重ね合わせる構造 Flutter UI 描画結果を Viewとして配置 Androidの View階層 映像表示部品
RasterとOSのUI処理が同じスレッドを共有 62
ネイティブ処理が長いと、Rasterの開始が遅れる Platform:OS・ネイティブ連携を処理する役割 従来のHybrid Compositionでは、Rasterと同じスレッドを使う 時間の流れ ネイティブ側の長い処理 FlutterのRaster処理 この処理が終わるまで、次の処理は待機 映像の再生中に、Flutter UIもカクつく原因になり得る
63
Android:Texture Layer Hybrid Composition もう1つは、Viewの描画結果を画像としてFlutterに渡す方式 Flutter UI Raster Impeller Flutterの
描画結果 この後、OSの合成・表示へ 映像表示部品 Viewの描画結果 Texture SurfaceViewを含むと、配置・重なり順に制約 64
Textureという名前でも、取り込む対象が違う 名前にTextureが含まれていても、入力が違います 動画を表示するTexture デコードした映像フレーム Flutterの描画に取り込む Texture Layer Hybrid Composition 映像表示部品を描画した画像
Flutterの描画に取り込む 65
Android:合成方式で、性能の傾向が変わる 比較すること Hybrid Composition Texture Layer Hybrid Composition 表示の仕方 Androidの画面部品として
Flutter UIと重ねる 再生画面を画像に描画し、 Flutter UIと合成する 性能の傾向 通常のView階層で表示 FlutterのFPSは低下しうる Flutterの描画性能を 保ちやすい 負荷・弱点 RasterとPlatformが統合 ネイティブ処理が描画を待たせる SurfaceViewの位置・重なり 高速スクロール時に制約 判断の軸 SurfaceViewを含む映像部品 通常のView階層で表示 通常のView描画が対象 SurfaceViewには不向き SurfaceView:映像を別のSurface(描画先)に出力するView 通常のViewの描画を取り込む方法では、配置・重なり順が崩れる場合がある 66
合成の同期負荷を減らす、Hybrid Composition++ FlutterとネイティブViewの描画結果を、OS側で合成する Flutterの描画結果 AndroidのOS側で合成 SurfaceFlinger 映像表示部品の描画結果 OSの同期機構で、2つの描画結果をまとめて反映 従来のHybrid Compositionの同期負荷を減らす
Flutter 3.44以降の実験的機能。利用には明示的な有効化が必要 67
Androidでは、Viewの合成方式を見ました 次はiOSの2つの表示経路を見ていきます 68
iOS Texture:映像フレームをFlutterが取得して描画 CVPixelBuffer=映像フレームの画素データを保持するiOSのオブジェクト Flutter Engine 描画内容 Flutter UI 表示する内容を指定 Scene
映像の位置・サイズ Raster CVPixelBuffer iOS側 Impellerで描画を準備 映像フレーム Texture GPU・OSへ続く 確認:フレーム取得・更新通知・復帰後の映像更新 69
iOS PlatformView:UIViewを画面に組み込む 同じ画面に表示 Flutterが描く 操作ボタン・再生位置 動画の映像 iOSのUIView 映像表示部品 Flutterの操作UI UIViewとFlutter
UIの重なり・タップ操作を確認 シークや復帰後も、映像が更新されるかを検証 70
iOS:Flutterの描画か、UIViewの表示機能か Texture 利点:映像の拡大・回転・クリップをFlutterで扱える 制約:映像フレームをFlutterへ渡す経路が必要 性能:フレーム取得・Flutter側の合成に負荷がかかる PlatformView 利点:UIViewの映像表示機能をそのまま利用できる 制約:ShaderMask・ColorFilteredは適用できない 性能:UIとの重ね合わせ・アニメーションに負荷がかかる iOSでは、必要な表示機能とFlutter側の加工・合成を基準に選ぶ。
71
迷ったらTextureで始め、要件からPlatformViewを選ぶ まずはTexture(video_playerの標準) FlutterのUIと映像をまとめて変形・合成できる ネイティブView固有の機能が不要なら、ここから 表示部品の機能を使うならPlatformView 既存の映像表示部品をアプリ内に組み込む AndroidはSurfaceViewの有無と合成方式も確認 採用前にUIと映像を同条件で計測する。最速を保証する選び方ではない。 72
第4章 カクつきの計測と 切り分け 73
カクついたとき、どこから調べるか まずは「UIが遅いのか、映像だけなのか」 症状から、計測する対象を絞りましょう 74
3つの対象ごとに、症状と計測項目を整理する 切り分ける対象 症状の例 最初に計測・記録する項目 Flutterの描画 UIのアニメーションや スクロールがカクつく UI / Rasterの処理時間
操作への応答 再生・シークの操作に 反応するまでが遅い 要求の往復時間 ネイティブ処理時間 映像の出力 UIは動くが 映像だけが進まない プレイヤーの状態 映像の更新・出力先 75
まず、実機のprofileモードで症状を再現する profile:実機で性能を計測するためのビルドモード Terminal flutter run --profile 同じ実機・動画・操作 同じ合成方式・Impeller設定 記録:OS /
SDK / パッケージ版 / 表示Hz / 温度 debugの処理時間で判断しない。初回表示と定常再生を分ける 76
DevToolsでカクついた時刻のフレームを調べる UIのカクつき:Performance viewで遅いフレームを選ぶ 記録して再現 カクついた時刻へ フレームを選ぶ 同じ操作を実行 Flutter frames chart
Frame analysis / Timeline 症状が起きた時刻と、選んだフレームを対応づける 77
Frame AnalysisでUIとRasterの時間を読む 選んだフレームのUI / Raster時間を、それぞれ確認する 78
UI / Rasterの処理時間を、1更新周期と比較する UIとRasterの合計ではなく、それぞれの処理時間で判断する 1,000 ÷ リフレッシュレート(Hz)= 更新周期(ms) 60 Hz:約16.7
ms 120 Hz:約8.3 ms 例:60 HzでUIが20 msなら、約16.7 msの更新周期を超える ※UI・Rasterが更新周期を超えていなくても、表示が遅れる場合がある 79
UIの処理時間が長いなら、Build・Layout・Paintを調べる 詳細トレースは原因調査でON。処理時間の比較ではOFFに戻す Build 再生位置の更新で BuildされるWidget Layout / Paint 配置・描画命令の どこに時間を使ったか
UIの処理時間 Track widget builds / Track layouts / Track paintsで対象を絞る 80
Rasterの処理時間が長いなら、描画・合成と待機を調べる Opacity:透明度を指定するWidget 処理時間が長い Raster区間 Blur:ぼかし ぼかしや半透明の処理を 1つずつ外す Opacity / Blur
/ 合成面積 / 同期 同じ操作で、外す前後の Raster時間を比較する 81
Rasterの処理時間が長いなら、描画・合成と待機を調べる Opacity:透明度を指定するWidget Blur:ぼかし UIやRasterの時間が短くても 処理時間が長い ぼかしや半透明の処理を Raster区間 1つずつ外す 操作の反応や映像が遅れることがあります 次は、Flutterの外側も調べます
Opacity / Blur / 合成面積 / 同期 同じ操作で、外す前後の Raster時間を比較する 82
操作応答と映像出力をネイティブ側で調べる 操作の遅れと、映像の停止を分けて追う 操作の応答が遅い メソッド呼び出し → ネイティブ処理 → 結果返却 操作要求の往復時間・実行スレッド 映像だけが止まる
再生状態・フレーム・出力先 Player / Texture / PlatformView 83
シークが遅いとき、どこで時間がかかっているか シーク=再生位置の変更。MethodChannelでDartからネイティブへ依頼する Dart ネイティブ側の処理 呼び出し #42 同じ呼び出しを #42 で識別 Dart・ネイティブ両側に記録
映像への反映は別に確認 結果受信 #42 処理開始 #42 シークを実行 結果返却 #42 Dart:呼び出し→結果受信 ネイティブ:処理開始→結果返却 84
シークが遅いとき、どこで時間がかかっている? シーク=再生位置の変更。MethodChannelでDartからネイティブへ依頼する Dart ネイティブ側の処理 結果が返るまでの時間が長くても 呼び出し #42 処理開始 #42 ネイティブ処理そのものが長いとは限りません
シークを実行 処理を始めるまでの待ち時間も調べます 結果受信 #42 結果返却 #42 Dart:呼び出し→結果受信 ネイティブ:処理開始→結果返却 85
Main threadが処理中だと、次の処理は待たされる UIとPlatformは標準で統合:iOS / Android(Flutter 3.29以降) 同じスレッドでは、複数の処理を同時に実行できない Main thread UI
Dartの処理 Platform OS・ネイティブ連携 長い処理の間は、UI更新や操作の処理が待機 Raster Androidの従来の Hybrid Compositionでは Rasterも統合 86
iOS:Instrumentsで処理と待機を調べる Instruments:Xcodeに付属する性能解析ツール Main threadの処理時間と待機時間を確認 Time Profiler CPU時間の大きい関数と その呼び出し元を確認 Thread State
Trace CPUを使っていない時間を確認 ロックや他の処理の完了を 待っていないかを調べる Main thread:OSのUI処理などを実行するスレッド 88
Android:System Traceで処理と待機を調べる System Trace:アプリとOSの動作を時系列に記録したもの Main threadの処理時間と待機時間を確認 記録する Android StudioのProfilerで記録 症状が出た時間帯を選ぶ
状態を確認する Running:CPUで実行中 Runnable:実行可能だがCPU待ち Sleepingなど:イベントやロック待ち Perfetto UI:記録したシステムトレースを表示・解析できるツール 89
操作応答と映像出力をネイティブ側で調べる 操作の遅れと、映像の停止を分けて追う メソッド呼び出し 操作UIは動くのに、映像だけが止まる → ネイティブ処理 → 結果返却 Flutterのフレームだけでは追えません 操作要求の往復時間・実行スレッド
プレイヤーの状態と映像出力を確認します 操作の応答が遅い 映像だけが止まる 再生状態・フレーム・出力先 Player / Texture / PlatformView 90
映像だけが止まるなら、再生状態も同じ時刻で追う Flutterのフレーム UI / Raster / 待ち 映像の再生 再生状態 /
drop / stall drop:映像フレームの破棄 stall:再生の停滞 91
映像だけが止まるなら、再生状態も同じ時刻で追う Flutterのフレーム UI / Raster / 待ち 再生位置は進むのに、映像が更新されない ここからは、プラグイン内部を調べる例です Textureの映像出力を、OSごとに確認します
映像の再生 drop:映像フレームの破棄 再生状態 / drop / stall stall:再生の停滞 92
iOS Texture:更新通知とバッファ取得を記録する iOS側のFlutter Texture APIの呼び出しを、プラグイン内で記録する デコード済み フレーム 更新の通知 Engineが取得 フレームの時刻・連番
textureFrameAvailable copyPixelBuffer 生成・取得したフレームと、更新通知の時刻を照合する 93
Android Texture:復帰時のSurface再生成を追う プラグイン内部の調査例:復帰後に映像が更新されない Surface:プレイヤーが映像を書き込む出力先 古いSurface 再取得 出力先を再接続 利用を終了 現在のSurface Playerへ設定
Surface再生成の前後で、Playerの出力先を確認する 94
第5章 原因別の 対策と検証 95
第4章では、原因を調べる方法を見ました ここからは、計測結果に合う対策を選びます 最初は、再生位置の表示がカクつく例です 96
計測結果に合わせて、試す対策を選ぶ 調べた対象 計測・記録した状況 試す対策 位置更新で Buildの対象が広がる 位置ラベルだけで 再生位置の通知を受ける Paint・合成に 時間がかかる
再描画の範囲と 描画効果を見直す 操作への応答 通知の受信処理などが Main threadを占有 通知頻度と 処理の実行先を見直す 映像の出力 シーク・復帰後に 映像が更新されない 更新通知と 出力先を確認する Flutterの描画 97
変更前:位置の更新で、ページ全体を再Build PlayerPage 再生位置の通知 映像Widget 位置ラベル 親のPlayerPageで通知を受けると、再Buildの対象が広がる 98
変更後:再生位置の通知を、位置ラベルだけで受ける PlayerPage 再生位置の通知 位置ラベルだけが 通知を受ける 映像Widget 位置ラベル 99
位置ラベルだけをValueListenableBuilderで更新する Dart(抜粋):初期化済みcontroller。formatPositionでmm:ssへ整形 Dart Column( children: [ VideoPlayer(controller), ValueListenableBuilder<VideoPlayerValue>( valueListenable: controller,
builder: (context, value, child) => Text( formatPosition(value.position), // 00:11 ), ), ], ) 再生位置の更新で、PlayerPage全体をsetStateしない 100
変更後:再生位置の通知を、位置ラベルだけで受ける PlayerPage 再生位置の通知 ここまでは、Buildの負荷が大きい場合です Paintに時間がかかっていたら 描画命令を作り直す範囲を確認します 位置ラベルだけが 通知を受ける 映像Widget 位置ラベル
101
変更前:変わらない子も、親と一緒にPaint 親がPaintを必要とし、子の内容は変わらない例 親のPaint 子のPaint 描画命令 同じ描画範囲にある子も、Paintの対象になり得る 102
変更後:RepaintBoundaryでPaintの範囲を分ける 親がPaintを必要とし、子の内容は変わらない例 RepaintBoundary 親のPaint 子の描画命令 合成へ 子が変わらなければ、描画命令を再利用できる 103
変更後:RepaintBoundaryでPaintの範囲を分ける 親がPaintを必要とし、子の内容は変わらない例 RepaintBoundary RepaintBoundaryが減らすのは、再Paintです 親のPaint Rasterの時間が長かった場合は 子の描画命令 合成へ 透明度やぼかしの負荷を確認します 子が変わらなければ、描画命令を再利用できる
104
単色の背景なら、Opacityを色のalphaに置き換える alpha:色の不透明度(0で透明、1で不透明) 変更前 変更後 Dart Opacity( opacity: 0.5, child: const
ColoredBox( color: Colors.black, child: SizedBox.expand(), ), ) Dart ColoredBox( color: Colors.black .withValues(alpha: 0.5), child: const SizedBox.expand(), ) 同じ見た目になるのは単色背景の例。子全体を半透明にする場合は置き換えない。 105
ぼかすのは背景か、子だけか? 対象から選ぶ 背景をぼかす BackdropFilter+必要なclip 子だけをぼかす ImageFiltered 何をぼかしたいかを保ったまま、処理する範囲を減らす 106
ぼかすのは背景か、子だけか? 対象から選ぶ BackdropFilter+必要なclip 背景をぼかす 再生位置の取得や通知に負荷があるなら まず、取得方法と通知頻度を確認します 映像フレームの更新とは別の処理です 子だけをぼかす ImageFiltered 何をぼかしたいかを保ったまま、処理する範囲を減らす
107
video_playerは、Dartから再生位置を定期取得する 再生中は100 ms間隔で取得(約10回/秒) 取得した値でcontroller.valueを更新 ValueListenableBuilderが値の変更を受け取る video_player 2.14.1時点。取得回数とBuild回数は必ずしも一致しない 108
再生位置をネイティブから通知する場合もある 独自プラグイン:ネイティブから通知する例 EventChannelでネイティブからDartへ通知 次は、送信頻度を下げる場合を見ていきます EventChannel:ネイティブからDartへイベントを送る仕組み 109
変更前:表示に不要な頻度で再生位置を通知 独自プラグインがEventChannelで通知する例 ネイティブの 再生位置 Dartが受け取った 再生位置 再生位置の表示 高頻度の通知 通知のたびに受信処理が発生し、UI更新の契機になる 110
変更後:送信側で、再生位置の通知頻度を下げる 独自プラグインがEventChannelで通知する例 ネイティブの 再生位置 Dartが受け取った 再生位置 再生位置の表示 必要な頻度へ Dartで間引くだけでは、ネイティブからの通信回数は減らない 111
再生位置の通知は減らす。完了・エラーは残す 通知の種類 減らしてよいか・その理由 再生位置の定期通知 表示に必要な頻度に減らす 最新の再生位置で表示を更新できる シーク完了・エラー 省略せず、発生順に届ける 省くと操作の完了・失敗が分からない 112
変更後:送信側で、再生位置の通知頻度を下げる 独自プラグインがEventChannelで通知する例 Dartが受け取った Main threadの処理が長かった場合は 再生位置の表示 再生位置 別スレッドへ移せる処理がないか確認します 必要な頻度へ APIのスレッド制約を守って分離します
ネイティブの 再生位置 Dartで間引くだけでは、ネイティブからの通信回数は減らない 113
Main threadには、そのスレッドで必要な処理だけ残す Main thread Main必須のAPI・View操作 別スレッド 分離できる計算・ファイル処理 APIのスレッド制約・呼び出し順序・破棄条件を確認 114
Android:チャネルのハンドラを別スレッドで実行する Kotlin:独自プラグインのonAttachedToEngine内(抜粋) Kotlin val messenger = binding.binaryMessenger val queue =
messenger.makeBackgroundTaskQueue() channel = MethodChannel( messenger, "app/video/metadata", StandardMethodCodec.INSTANCE, queue ) channel.setMethodCallHandler(this) iOSにもFlutterBinaryMessenger.makeBackgroundTaskQueue()がある 対象は分離できる計算・I/O。View操作やPlayer APIは指定スレッドで実行する 115
最後に、再Buildを減らした例へ戻ります 変更前と変更後を、同じ条件で比較します 処理時間と、映像・操作を確認します 116
再生位置を受け取るWidgetだけを変えて比較 A:PlayerPageで受信 ページ全体が再Build 同じ端末・動画・UI・ 操作 B:位置ラベルで受信 位置ラベルだけ再Build 動画・表示方式・通知頻度は変えず、再Buildの範囲を比較 117
通常再生と、シーク・復帰を分けて検証する 再Build削減の検証例:AとBで同じ動画・操作を比較 通常再生 シーク・復帰 UI / Rasterの 処理時間を計測 映像更新と操作が 正常かを確認
A→B→A→B A:PlayerPageで受信 B:位置ラベルで受信 通常再生は20秒の例。初回表示・シーク・復帰は別の区間で確認 118
処理時間が改善し、映像と操作が正常なら採用する 処理時間は詳細トレースをOFFにし、変更前後を同じ設定で比較する 対象 変更前後で確認すること 再Buildの範囲 PlayerPage・映像Widgetの不要なBuildが減ったか UI / Raster 処理時間のp95・最大値、1更新周期を超えた割合
映像・操作 再生位置表示・映像更新・シーク・復帰が正常か p95:計測した処理時間の95%が、この値以下 119
最後に、プレイヤーの選び方を振り返ります video_playerで必要な機能を扱えるか 表示方式はTextureか、PlatformViewか 120
「再生の操作」と「映像の表示」は、別々に選ぶ 再生を操作する video_player または独自プラグイン 映像を表示する Texture またはPlatformView 121
video_playerで、必要な機能を扱える? 標準APIで扱える 標準APIにない例 再生・シーク・音声/画質選択 video_playerを使う PiPの開始・終了 DRMのライセンス認証設定 拡張・別プラグイン等を検討 video_player 2.14.1時点。OS・配信形式によって対応が異なる
PiP(ピクチャ・イン・ピクチャ)が必要でも、PlatformView必須とは限らない 122
AndroidのPlatformViewは、合成方式も選ぶ 対応条件を満たす端末 Android API 34以降 Impeller Vulkan Hybrid Composition++ を検討
対応条件を満たさない端末 古いOS・Vulkan非対応など 設定済みの既存方式へ フォールバック Flutter 3.44以降で利用可能(実験的・オプトイン) AndroidManifest.xmlで明示的に有効化 123
【おまけ】プレイヤー構成診断 video_playerのAPIで 必要な機能を扱える? Yes No video_player 拡張・独自実装など ネイティブViewの 表示機能が必要? ネイティブViewの
表示機能が必要? Yes No Yes No PlatformView まずTexture PlatformView まずTexture Hybrid Composition++は実験的・オプトイン(Flutter 3.44以降) Android API 34以降・Impeller Vulkanが必要。設定で有効化 124
ご清聴ありがとうございました Yoshiki Naruo
参考資料 SurfaceProducer API api.flutter.dev/javadoc/io/flutter/view/TextureRegistry.SurfaceProducer.html FlutterTextureRegistry textureFrameAvailable api.flutter.dev/ios-embedder/protocol_flutter_texture_registry-p.html FrameTiming API api.flutter.dev/flutter/dart-ui/FrameTiming-class.html
Platform channels and threading docs.flutter.dev/platform-integration/platform-channels Flutter architecture / thread merging docs.flutter.dev/resources/architectural-overview Android Platform Views(Hybrid Composition++ / Texture Layer) docs.flutter.dev/platform-integration/android/platform-views 126