Using video_player package with Flutter Hooks to play a background fullscreen video

970 views Asked by At

I have a Home Screen Widget, that plays a fullscreen background video using the video_player package. This code works fine for me:

class HomeScreen extends StatefulWidget {
  HomeScreen({Key key}) : super(key: key);

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  VideoPlayerController _controller;

  void initState() {
    super.initState();
    // Pointing the video controller to mylocal asset.
    _controller = VideoPlayerController.asset("assets/waterfall.mp4");

    _controller.initialize().then((_) {
      // Once the video has been loaded we play the video and set looping to true.
      _controller.play();
      _controller.setLooping(true);
      // Ensure the first frame is shown after the video is initialized.
      setState(() {});
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        body: Stack(
          children: <Widget>[
            SizedBox.expand(
              child: FittedBox(
                // If your background video doesn't look right, try changing the BoxFit property.
                // BoxFit.fill created the look I was going for.
                fit: BoxFit.fill,
                child: SizedBox(
                  width: _controller.value.size?.width ?? 0,
                  height: _controller.value.size?.height ?? 0,
                  child: VideoPlayer(_controller),
                ),
              ),
            ),
            Container(
              child: Center(
                child: Text('Hello!'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

The question is, how can I implement this using flutter Hooks? I understand that I have to use useEffect() to implement the functionality of initState() and dispose(), useFuture() and maybe useMemoized() to handle asynchronous _controller.initialize() call and what possibly else? But, I cannot glue them to get the desired result. Can anyone indicate to me the "using Hooks" implementation of the above code?

1

There are 1 answers

0
bperreault On

I was looking for the answer to how to convert a VideoPlayer demo from StatefulWidget to HookWidget when I came across this question. I've come up with something that works so I'll post it here since there is nothing elsewhere that I could find and some others are hitting this page looking for an answer.

I used a viewmodel. The video controller is a property of the viewmodel. This code will not compile since some of the controls are not included. But it will demonstrate the structure and incorporation of the viewmodel.

Here's the widget file:

import 'package:flutter/foundation.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:video_player/video_player.dart';

import 'intro_viewmodel.dart';

class IntroPage extends HookWidget {
  Future<void> saveAndGetStarted(BuildContext context) async {
    final IntroViewModel introViewModel = context.read(introViewModelProvider);
    await introViewModel.completeIntro();
  }

  Future<void> onNext(BuildContext context) async {
    final IntroViewModel introViewModel = context.read(introViewModelProvider);
    await introViewModel.incrementIntro();
  }

  final List<SliderModel> slides = [
    SliderModel(
        description: 'A word with you before you get started.\n',
        title: 'Why This App?',
        localImageSrc: 'media/Screen1-Movingforward-pana.svg',
        backgroundColor: Colors.lightGray),
    SliderModel(
        description: 'This information will help the app be more accurate\n',
        title: 'Personal Profile',
        localImageSrc: 'media/Screen2-Teaching-cuate.svg',
        backgroundColor: Colors.lightGray)
  ];

  @override
  Widget build(BuildContext context) {
    final IntroViewModel introViewModel = context.read(introViewModelProvider);

    return Scaffold(
        body: Padding(
      padding: const EdgeInsets.all(16.0),
      child: Center(
        child: Column(
          children: [
            Text(
              slides[introViewModel.index].description,
              style: Theme.of(context).textTheme.headline5,
              textAlign: TextAlign.center,
            ),
            Expanded(
                child: FractionallySizedBox(
              widthFactor: .98,
              heightFactor: .5,
              child: VideoPlayer(introViewModel.videoController),
            )),
            Align(
              alignment: Alignment.bottomCenter,
              child: CustomRaisedButton(
                onPressed: () {
                  if (introViewModel.index == slides.length - 1) {
                    saveAndGetStarted(context);
                  } else {
                    onNext(context);
                  }
                },
                color: Theme.of(context).accentColor,
                borderRadius: 15,
                height: 50,
                child: Text(
                  introViewModel.index == 0
                      ? 'Continue'
                      : 'Save and Get Started',
                  style: Theme.of(context)
                      .textTheme
                      .headline5
                      .copyWith(color: Colors.white),
                ),
              ),
            ),
          ],
        ),
      ),
    ));
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(IterableProperty<SliderModel>('slides', slides));
  }
}

And here is the viewmodel code

import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:video_player/video_player.dart';
import '../top_level_providers.dart';

final introViewModelProvider = ChangeNotifierProvider<IntroViewModel>((ref) {
//this singleton class provides global access to selected variables
  final SharedPreferencesService localSharedPreferencesService =
      ref.watch(sharedPreferencesService);
  return IntroViewModel(localSharedPreferencesService);
});

class IntroViewModel extends ChangeNotifier {
  IntroViewModel(this.localSharedPreferencesService) : super() {
    state = localSharedPreferencesService?.isIntroComplete();

    // Pointing the video controller to my local asset.
    videoController = VideoPlayerController.asset('media/test_search.mp4');

    videoController.initialize().then((_) {
      // Once the video has been loaded we play the video and set looping to true.
      //  not autoplaying yet
//  videoController.play();
      //    videoController.setLooping(true);
    });
  }

  final SharedPreferencesService localSharedPreferencesService;
  VideoPlayerController videoController;
  bool state = false;
  int index = 0;

  Future<void> completeIntro() async {
    await localSharedPreferencesService.setIntroComplete();
    state = true;
    notifyListeners();
  }

  Future<void> incrementIntro() async {
    ++index;
    notifyListeners();
  }

  bool get isIntroComplete => state;
}