TEXTBOOK SECTION / AI LEARNING

video_playerで動画を再生する

Flutterアプリケーション開発概論の「【Flutter】TikTokのUIから学ぶ画面構築。画面を「部品」と「構造」で組み立てる。」より、video_playerで動画を再生するを解説。生成AI、AI活用、DX、業務改善を実践しながら学べるオンライン教材です。

第7節【Flutter】TikTokのUIから学ぶ画面構築。画面を「部品」と「構造」で組み立てる。Flutter / iOS / Android / MacOS / Windows / 基礎から学ぶ / 開発 / アプリ開発

OVERVIEW

この節で学べること

概要を表示する
項目内容
教材名Flutterアプリケーション開発概論
章【Flutter】TikTokのUIから学ぶ画面構築。画面を「部品」と「構造」で組み立てる。
節video_playerで動画を再生する
カテゴリFlutter / iOS / Android / MacOS / Windows / 基礎から学ぶ / 開発 / アプリ開発
学習内容生成AI、AI活用、DX、業務改善を実践しながら理解するための教材です。

TABLE OF CONTENTS

目次

目次を表示する

CONTENT

ここから

この節で学ぶこと

前回の 4-6 では、PageView.builder を使って、TikTokのように上下スワイプで画面を切り替える仕組みを作りました。

前回の段階では、まだ本物の動画は再生していません。

代わりに、次のような仮の動画エリアを表示していました。

PET
FOOD
ROOM

今回の 4-7 では、いよいよ本物の動画を再生します。

DartPadでも動かしやすいように、Flutter公式が公開している .mp4 動画URLを使います。

この節で大切なのは、次の一文です。

動画を再生するには、video_playerパッケージとVideoPlayerControllerを使う。

まず今回作るもの

この節では、次のような画面を作ります。

前回までの VideoPlaceholder を、本物の動画表示に置き換えます。

変更前
VideoPlaceholder

変更後
VideoBackground

最終的には、TikTok風アプリの背景として動画を全画面表示します。

video_playerとは何か

video_player は、Flutterで動画を再生するためのパッケージです。

パッケージとは、Flutterに機能を追加するための部品です。

Flutter本体だけでも多くのUIは作れますが、動画再生のような機能は、専用のパッケージを使うと便利です。

package = Flutterに機能を追加する道具
video_player = 動画再生機能を追加するパッケージ

DartPadで使う場合は、Packagesに次を追加します。

video_player: ^2.9.2

新しい言葉:VideoPlayerControllerとは何か

動画を再生するには、VideoPlayerController を使います。

Controller とは、何かを操作・管理するためのものです。

今回の場合は、動画を操作します。

VideoPlayerController
├─ 動画を読み込む
├─ 動画を再生する
├─ 動画を停止する
├─ 音量を設定する
└─ ループ再生を設定する

コードでは、次のように作ります。

final controller = VideoPlayerController.networkUrl(
  Uri.parse(video.videoUrl),
);

これは、次の意味です。

video.videoUrl の動画を再生するためのControllerを作る。

新しい言葉:initializeとは何か

動画は、URLを指定しただけではすぐに使えません。

まず、動画を読み込んで、再生できる準備をする必要があります。

その準備をするのが initialize です。

await controller.initialize();

初心者向けには、次のように理解してください。

initialize = 動画を使う準備をする処理

動画のサイズや長さなどを取得するためにも、initialize が必要です。

新しい言葉:Futureとasync / await

動画の読み込みには時間がかかります。

そのため、Dartでは Future、async、await という書き方を使います。

Future<void> initializeVideo() async {
  await controller.initialize();
}

ここでは、深く考えすぎなくて大丈夫です。

まずは次の理解で進めます。

用語初心者向けの意味
Futureすぐには終わらない処理
async時間がかかる処理を書く印
await処理が終わるまで待つ

動画読み込みは時間がかかるので、await controller.initialize() と書きます。

新しい言葉:StatefulWidgetが必要になる理由

前回までの画面は、表示するだけでした。

しかし、動画再生では次のような「変化」があります。

動画を読み込み中
↓
読み込み完了
↓
再生する
↓
一時停止する

このように、画面の状態が変わるため、StatefulWidget を使います。

StatelessWidget
表示が変わらない部品

StatefulWidget
状態が変わる部品

動画再生では、VideoPlayerController を作り、読み込み完了後に画面を更新する必要があります。

そのため、今回のメイン画面は StatefulWidget にします。

まずは1本の動画を再生する

最初から縦スワイプに戻すと難しいので、まずは1本の動画だけを再生してみます。

DartPadのPackagesに video_player: ^2.9.2 を追加したうえで、次のコードを貼り付けてください。(最初から入っている場合あり)

import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';

void main() {
  runApp(const VideoPlayerPracticeApp());
}

class VideoPlayerPracticeApp extends StatelessWidget {
  const VideoPlayerPracticeApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: VideoPlayerPracticePage(),
    );
  }
}

class VideoPlayerPracticePage extends StatefulWidget {
  const VideoPlayerPracticePage({super.key});

  @override
  State<VideoPlayerPracticePage> createState() =>
      _VideoPlayerPracticePageState();
}

class _VideoPlayerPracticePageState extends State<VideoPlayerPracticePage> {
  late final VideoPlayerController controller;

  bool isReady = false;

  @override
  void initState() {
    super.initState();
    initializeVideo();
  }

  Future<void> initializeVideo() async {
    controller = VideoPlayerController.networkUrl(
      Uri.parse(
        'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4',
      ),
    );

    await controller.initialize();

    controller
      ..setLooping(true)
      ..setVolume(0)
      ..play();

    setState(() {
      isReady = true;
    });
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  void togglePlay() {
    setState(() {
      if (controller.value.isPlaying) {
        controller.pause();
      } else {
        controller.play();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    if (!isReady) {
      return const Scaffold(
        backgroundColor: Colors.black,
        body: Center(
          child: CircularProgressIndicator(
            color: Colors.white,
          ),
        ),
      );
    }

    return Scaffold(
      backgroundColor: Colors.black,
      body: GestureDetector(
        onTap: togglePlay,
        child: Center(
          child: AspectRatio(
            aspectRatio: controller.value.aspectRatio,
            child: VideoPlayer(controller),
          ),
        ),
      ),
    );
  }
}

実行して確認すること

実行すると、黒背景の中央に動画が表示されます。

動画部分をタップすると、一時停止と再生を切り替えられます。

タップ
↓
再生中なら停止

もう一度タップ
↓
停止中なら再生

ここでは、次のことを確認してください。

- video_playerで動画が表示できる
- VideoPlayerControllerで動画を管理している
- initializeが終わるまで読み込み表示を出している
- GestureDetectorでタップ操作を受け取っている

コードを順番に理解する

import

import 'package:video_player/video_player.dart';

これは、video_player パッケージを使うための読み込みです。

このimportがないと、VideoPlayerController や VideoPlayer が使えません。

controllerを用意する

late final VideoPlayerController controller;

ここでは、動画を管理するcontrollerを用意しています。

late は、「あとで必ず値を入れる」という意味です。

このコードでは、initState の中で initializeVideo() を呼び、その中でcontrollerを作ります。

initStateとは何か

@override
void initState() {
  super.initState();
  initializeVideo();
}

initState は、StatefulWidgetが最初に作られたときに一度だけ呼ばれる場所です。

初心者向けには、次のように覚えてください。

initState = 最初に一度だけ準備をする場所

動画再生では、最初にcontrollerを作り、動画を読み込む必要があります。

そのため、initState で initializeVideo() を呼びます。

動画を読み込む

controller = VideoPlayerController.networkUrl(
  Uri.parse(
    'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4',
  ),
);

これは、ネット上の動画URLを指定してcontrollerを作っています。

networkUrl は、インターネット上の動画を再生するときに使います。

initializeする

await controller.initialize();

これは、動画を使える状態にする処理です。

動画の読み込みが終わるまで待ちます。

ループ、音量、再生

controller
  ..setLooping(true)
  ..setVolume(0)
  ..play();

この書き方は少し見慣れないかもしれません。

.. は、同じcontrollerに対して続けて処理を書くための記法です。

普通に書くと、次と同じです。

controller.setLooping(true);
controller.setVolume(0);
controller.play();

それぞれの意味は次の通りです。

コード意味
setLooping(true)繰り返し再生する
setVolume(0)音量を0にする
play()再生する

DartPadやブラウザでは、自動再生の制限があるため、音量を0にしておくと再生されやすくなります。

setStateで画面を更新する

setState(() {
  isReady = true;
});

setState は、状態が変わったことをFlutterに知らせるための関数です。

ここでは、動画の準備が終わったので、isReady を true にしています。

isReady = false
↓
読み込み中画面を表示

isReady = true
↓
動画画面を表示

setState を呼ぶことで、Flutterが画面を再描画します。

disposeでcontrollerを片付ける

@override
void dispose() {
  controller.dispose();
  super.dispose();
}

dispose は、Widgetが不要になったときに呼ばれる場所です。

動画controllerは、使い終わったら片付ける必要があります。

初心者向けには、次のように覚えてください。

dispose = 使い終わったものを片付ける場所

動画controllerを片付けないと、メモリを無駄に使ってしまうことがあります。

VideoPlayerで動画を表示する

VideoPlayer(controller)

VideoPlayer は、実際に動画を画面に表示するWidgetです。

ただし、VideoPlayer はcontrollerが必要です。

VideoPlayerController
↓
動画を管理する

VideoPlayer
↓
動画を画面に表示する

この2つはセットで考えると分かりやすいです。

AspectRatioとは何か

AspectRatio(
  aspectRatio: controller.value.aspectRatio,
  child: VideoPlayer(controller),
)

AspectRatio は、縦横比を保つためのWidgetです。

動画には、それぞれ縦横比があります。

横長の動画を無理に正方形にすると、映像がつぶれて見えます。

AspectRatio を使うと、動画本来の比率で表示できます。

初心者向けには、次のように理解してください。

AspectRatio = 縦横比を守って表示するWidget

GestureDetectorでタップを受け取る

GestureDetector(
  onTap: togglePlay,
  child: Center(
    child: AspectRatio(...),
  ),
)

GestureDetector は、タップなどの操作を検知するWidgetです。

ここでは、動画をタップすると togglePlay が呼ばれます。

void togglePlay() {
  setState(() {
    if (controller.value.isPlaying) {
      controller.pause();
    } else {
      controller.play();
    }
  });
}

この処理は、次の意味です。

もし再生中なら停止する
そうでなければ再生する

次に全画面背景として動画を表示する

TikTok風アプリでは、動画は中央に小さく表示するのではなく、画面全体に広げます。

そのため、次は FittedBox を使います。

新しい言葉:FittedBoxとは何か

FittedBox は、子Widgetを指定した範囲に合わせて拡大・縮小するWidgetです。

FittedBox(
  fit: BoxFit.cover,
  child: ...
)

BoxFit.cover は、画面全体を埋めるように表示する指定です。

動画の一部が切れることはありますが、TikTok風の全画面背景に近い見た目になります。

BoxFit.cover
↓
画面全体を埋める
↓
必要なら一部を切り取る

全画面背景として動画を表示するコード

次は、先ほどのコードをTikTok風の全画面背景に近づけます。

DartPadに次のコードを貼り付けてください。

import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';

void main() {
  runApp(const FullScreenVideoPracticeApp());
}

class FullScreenVideoPracticeApp extends StatelessWidget {
  const FullScreenVideoPracticeApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: FullScreenVideoPracticePage(),
    );
  }
}

class FullScreenVideoPracticePage extends StatefulWidget {
  const FullScreenVideoPracticePage({super.key});

  @override
  State<FullScreenVideoPracticePage> createState() =>
      _FullScreenVideoPracticePageState();
}

class _FullScreenVideoPracticePageState
    extends State<FullScreenVideoPracticePage> {
  late final VideoPlayerController controller;

  bool isReady = false;

  @override
  void initState() {
    super.initState();
    initializeVideo();
  }

  Future<void> initializeVideo() async {
    controller = VideoPlayerController.networkUrl(
      Uri.parse(
        'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4',
      ),
    );

    await controller.initialize();

    controller
      ..setLooping(true)
      ..setVolume(0)
      ..play();

    setState(() {
      isReady = true;
    });
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  void togglePlay() {
    setState(() {
      if (controller.value.isPlaying) {
        controller.pause();
      } else {
        controller.play();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    if (!isReady) {
      return const Scaffold(
        backgroundColor: Colors.black,
        body: Center(
          child: CircularProgressIndicator(
            color: Colors.white,
          ),
        ),
      );
    }

    return Scaffold(
      backgroundColor: Colors.black,
      body: Stack(
        children: [
          Positioned.fill(
            child: GestureDetector(
              onTap: togglePlay,
              child: VideoBackground(controller: controller),
            ),
          ),
          const Positioned.fill(
            child: VideoGradientOverlay(),
          ),
          const Positioned(
            top: 52,
            left: 0,
            right: 0,
            child: TopNavigationPractice(),
          ),
          const Positioned(
            left: 16,
            right: 92,
            bottom: 36,
            child: BottomVideoInfoPractice(),
          ),
        ],
      ),
    );
  }
}

class VideoBackground extends StatelessWidget {
  const VideoBackground({
    super.key,
    required this.controller,
  });

  final VideoPlayerController controller;

  @override
  Widget build(BuildContext context) {
    final size = controller.value.size;

    return Container(
      color: Colors.black,
      child: FittedBox(
        fit: BoxFit.cover,
        child: SizedBox(
          width: size.width,
          height: size.height,
          child: VideoPlayer(controller),
        ),
      ),
    );
  }
}

class VideoGradientOverlay extends StatelessWidget {
  const VideoGradientOverlay({super.key});

  @override
  Widget build(BuildContext context) {
    return IgnorePointer(
      child: DecoratedBox(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            colors: [
              Colors.black.withOpacity(0.45),
              Colors.transparent,
              Colors.transparent,
              Colors.black.withOpacity(0.86),
            ],
            stops: const [0.0, 0.24, 0.58, 1.0],
          ),
        ),
      ),
    );
  }
}

class TopNavigationPractice extends StatelessWidget {
  const TopNavigationPractice({super.key});

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      bottom: false,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(
            'フォロー中',
            style: TextStyle(
              color: Colors.white.withOpacity(0.6),
              fontSize: 15,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(width: 18),
          const Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(
                'おすすめ',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 16,
                  fontWeight: FontWeight.bold,
                ),
              ),
              SizedBox(height: 4),
              SizedBox(
                width: 28,
                height: 3,
                child: DecoratedBox(
                  decoration: BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.all(
                      Radius.circular(999),
                    ),
                  ),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class BottomVideoInfoPractice extends StatelessWidget {
  const BottomVideoInfoPractice({super.key});

  @override
  Widget build(BuildContext context) {
    return const SafeArea(
      top: false,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            '@pet_cafe_diary',
            style: TextStyle(
              color: Colors.white,
              fontSize: 16,
              fontWeight: FontWeight.bold,
              shadows: [
                Shadow(
                  color: Colors.black87,
                  blurRadius: 8,
                ),
              ],
            ),
          ),
          SizedBox(height: 8),
          Text(
            '小さな命の動きは、見ているだけで少しやさしい気持ちになる。今日はペットカフェ風の癒し動画。',
            maxLines: 2,
            overflow: TextOverflow.ellipsis,
            style: TextStyle(
              color: Colors.white,
              fontSize: 14,
              height: 1.35,
              shadows: [
                Shadow(
                  color: Colors.black87,
                  blurRadius: 8,
                ),
              ],
            ),
          ),
          SizedBox(height: 10),
          Row(
            children: [
              Icon(
                Icons.music_note_rounded,
                color: Colors.white,
                size: 17,
              ),
              SizedBox(width: 6),
              Expanded(
                child: Text(
                  'Healing Cafe Sound - Pet Cafe Diary',
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 13,
                    fontWeight: FontWeight.w600,
                    shadows: [
                      Shadow(
                        color: Colors.black87,
                        blurRadius: 8,
                      ),
                    ],
                  ),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

実行して確認すること

このコードを実行すると、動画が画面全体の背景として表示されます。

さらに、その上に次のUIが重なっています。

上部:フォロー中 / おすすめ
下部:ユーザー名 / キャプション / 音源情報

ここで確認するポイントは、次の3つです。

1. 動画をVideoPlayerで再生している
2. FittedBox + BoxFit.coverで全画面背景にしている
3. Stackで動画の上に文字を重ねている

これで、かなりTikTok風アプリの見た目に近づきました。

VideoBackgroundの仕組み

VideoBackground は、動画を背景として表示するWidgetです。

class VideoBackground extends StatelessWidget {
  const VideoBackground({
    super.key,
    required this.controller,
  });

  final VideoPlayerController controller;

ここでは、外から VideoPlayerController を受け取っています。

required this.controller,

そして、VideoPlayer(controller) で動画を表示します。

VideoPlayer(controller)

size.widthとsize.height

final size = controller.value.size;

動画には、元のサイズがあります。

そのサイズを取得して、SizedBox に指定しています。

SizedBox(
  width: size.width,
  height: size.height,
  child: VideoPlayer(controller),
)

このようにすることで、動画本来の縦横比を保ちながら、FittedBox で画面全体に広げられます。

グラデーションを重ねる理由

動画の上に白文字を置くと、背景によっては文字が読みにくくなることがあります。

そこで、VideoGradientOverlay を重ねています。

const Positioned.fill(
  child: VideoGradientOverlay(),
),

VideoGradientOverlay は、上と下を少し暗くする半透明のグラデーションです。

動画そのまま
↓
白文字が読みにくいことがある

動画 + 黒グラデーション
↓
白文字が読みやすくなる

TikTok風UIでは、下部に文字が多いため、下を少し暗くすると読みやすくなります。

IgnorePointerとは何か

VideoGradientOverlay では、IgnorePointer を使っています。

return IgnorePointer(
  child: DecoratedBox(...),
);

IgnorePointer は、タップなどの操作を無視するためのWidgetです。

グラデーションは見た目のためだけに重ねています。

もしグラデーションがタップを受け取ってしまうと、動画のタップ操作が邪魔されることがあります。

そこで、IgnorePointer を使います。

初心者向けには、次のように理解してください。

IgnorePointer = 見た目だけ表示して、タップ操作は受け取らないようにするWidget

この節で覚える動画再生の流れ

動画再生の基本の流れは、次の通りです。

1. VideoPlayerControllerを作る
2. initializeで動画を準備する
3. setLoopingでループ設定する
4. setVolumeで音量を設定する
5. playで再生する
6. VideoPlayerで画面に表示する
7. disposeで片付ける

コードで見ると、次の流れです。

controller = VideoPlayerController.networkUrl(
  Uri.parse(videoUrl),
);

await controller.initialize();

controller
  ..setLooping(true)
  ..setVolume(0)
  ..play();

表示するときは、次のようにします。

VideoPlayer(controller)

使い終わったら、次のように片付けます。

controller.dispose();

手を動かす練習1:別の動画に変える

動画URLを次の部分で指定しています。

'https://flutter.github.io/assets-for-api-docs/assets/videos/butterfly.mp4'

これを次のように変えてみてください。

'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'

動画が変わります。

この練習で、動画URLを変えると表示される動画も変わることを確認できます。

手を動かす練習2:ループを止める

次の部分を探してください。

..setLooping(true)

これを次のように変えてみます。

..setLooping(false)

動画が最後まで再生されると停止します。

TikTok風アプリでは、基本的に繰り返し再生したいので、最終的には true に戻してください。

手を動かす練習3:音量を変える

次の部分を探してください。

..setVolume(0)

これを次のように変えてみます。

..setVolume(0.5)

環境によっては音が出ます。

ただし、DartPadやブラウザでは自動再生の制限があるため、音あり自動再生は止められる場合があります。

学習中は、まず setVolume(0) のままで進めるのがおすすめです。

手を動かす練習4:グラデーションを濃くする

次の部分を探してください。

Colors.black.withOpacity(0.86),

これを次のように変えてみます。

Colors.black.withOpacity(0.95),

画面下部がさらに暗くなります。

下部の文字が読みやすくなる反面、動画は暗く見えます。

このように、UIでは「見やすさ」と「映像の見え方」のバランスを調整します。

よくあるつまずき1:動画が表示されない

動画が表示されない場合、まず次を確認してください。

- DartPadのPackagesにvideo_playerを追加しているか
- import 'package:video_player/video_player.dart'; があるか
- 動画URLが正しいか
- initializeが終わる前にVideoPlayerを表示していないか

今回のコードでは、isReady を使って、初期化が終わるまでローディングを表示しています。

if (!isReady) {
  return const Scaffold(
    backgroundColor: Colors.black,
    body: Center(
      child: CircularProgressIndicator(
        color: Colors.white,
      ),
    ),
  );
}

よくあるつまずき2:initializeを忘れる

VideoPlayerController は、作っただけでは動画を表示できません。

必ず次のように初期化します。

await controller.initialize();

initialize を忘れると、動画のサイズが分からず、正しく表示できないことがあります。

よくあるつまずき3:disposeを忘れる

controllerを作ったら、使い終わったときに片付けます。

@override
void dispose() {
  controller.dispose();
  super.dispose();
}

動画やアニメーションのcontrollerは、dispose で片付ける習慣をつけるとよいです。

よくあるつまずき4:constをつけすぎる

動画controllerのように、状態が変わるものを扱うWidgetでは、すべてに const をつけられるわけではありません。

たとえば、次のようなコードは controller が変数なので、const にはできません。

VideoBackground(controller: controller)

const でエラーになる場合は、外して大丈夫です。

よくあるつまずき5:全画面にならない

動画を全画面背景にするには、次の組み合わせが大切です。

Positioned.fill(
  child: VideoBackground(controller: controller),
)

さらに、VideoBackground の中で FittedBox を使います。

FittedBox(
  fit: BoxFit.cover,
  child: SizedBox(
    width: size.width,
    height: size.height,
    child: VideoPlayer(controller),
  ),
)

この組み合わせで、動画が画面いっぱいに広がります。

この節の確認問題

確認問題1

video_player は何のために使いますか。

答え

Flutterで動画を再生するために使います。

確認問題2

VideoPlayerController は何をするものですか。

答え

動画を読み込んだり、再生・停止したりするための管理役です。

確認問題3

initialize は何のために必要ですか。

答え

動画を使える状態に準備するためです。

動画のサイズなどを取得するためにも必要です。

確認問題4

VideoPlayer は何をするWidgetですか。

答え

VideoPlayerController が管理している動画を、画面に表示するWidgetです。

確認問題5

FittedBox と BoxFit.cover は何のために使いましたか。

答え

動画を画面全体に広げ、全画面背景のように表示するためです。

確認問題6

dispose は何のために使いますか。

答え

使い終わったcontrollerを片付けるためです。

この節のまとめ

この節では、video_player を使って、DartPad上で動画を再生しました。

最初は1本の動画を中央に表示し、その後、TikTok風に全画面背景として表示しました。

動画再生の基本の流れは、次の通りです。

VideoPlayerControllerを作る
↓
initializeで準備する
↓
setLoopingやsetVolumeを設定する
↓
playで再生する
↓
VideoPlayerで表示する
↓
disposeで片付ける

また、TikTok風UIでは、動画を全画面背景として表示し、その上に文字やボタンを重ねます。

Stack
├─ VideoBackground
├─ VideoGradientOverlay
├─ TopNavigation
└─ BottomVideoInfo

この節で一番大切なのは、次の一文です。

VideoPlayerControllerで動画を管理し、VideoPlayerで画面に表示する。

次の節では、今回作った動画再生と、前回学んだ PageView.builder を組み合わせます。

複数の動画を上下スワイプで切り替え、TikTok風アプリの中心部分を作っていきます。

FAQ

よくある質問

video_playerで動画を再生するは医療関係者向けだけの内容ですか。
医療分野の例が含まれる場合もありますが、医療関係者だけに限定した内容ではありません。生成AI、AI活用、DX、業務改善、プロトタイプ開発など、一般的なAI学習の事例として読める内容です。
AI初心者でも読めますか。
はい。AIをこれから学ぶ方、数学が苦手な方、仕事でAIを使いたい方にも読み進めやすいように、教材の章と節の流れに沿って整理しています。
サムネイル画像は必ず表示されますか。
はい。教材にcoverUrlが設定されている場合はその画像を表示し、未設定の場合は代替サムネイル画像を表示します。
Flutterアプリケーション開発概論のほかの章も読めますか。
はい。教材トップから章立てを確認でき、前後の節へもページ下部のナビゲーションから移動できます。