Flutter: Offline playback for video_player

In this story we will cover how I approached adding offline playback of HLS streams to one of our mobile apps.

Flutter: Offline playback for video_player

In this story we will cover how I approached adding offline playback of HLS streams to one of our mobile apps.


Last story I promised to write about modifying an endorsed federated plugin in Flutter — so here we go.
Along the way we will learn about alternative repositories to pub.dev and how to utilize them, as well as utilizing pigeon for type safe PlatformChannel communication.

Step 1: Idea and Research

As already noted, we use HLS for playback in our app.
The first thought that came to my mind was: "I'll just download the m3u8 playlist and all relevant chunks to some place on the filesystem, that should be fairly easy."

Ha, of course not.

While this approach works on Android, you quickly find out that it does not on iOS.
See following references:
https://github.com/flutter/flutter/issues/101166
https://developer.apple.com/forums/thread/69357

It turns out there is a dedicated API not only in the iOS SDK but also in the Android SDK for this task, which is not implemented by any open source player atm (afaik), despite my fork ;-)

Step 2: Getting familiar with the native API's

This step is mostly about reading docs and trying the official samples.
One very viable approach is to write a very simple application which does just the minimal work to utilize the necessary APIs.

Once you do that, you will gather enough knowledge to procede with the real implementation.

I used following resources to get an understanding of what to do and how to do it:
https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/MediaPlaybackGuide/Contents/Resources/en.lproj/HTTPLiveStreaming/HTTPLiveStreaming.html#//apple_ref/doc/uid/TP40016757-CH11-SW3
https://github.com/google/ExoPlayer/tree/release-v2/demos/main

Step 3: Implementing the flutter/native bridge

Originally I wanted to implement the offline feature as part of a separate plugin based on the video_player plugin, but I couldn't figure out how to share objects in a good way, where I could keep changes to video_player either non-invasive or ideally not make changes to video_player at all.
So I ended up adding methods for download management to the video_player interface instead. Which is invasive but at least does not require an additional plugin and was easy to do.

Pigeon

The video_player plugin is using the Pigeon package to create type safe messages for the PlatformChannel.

While it looked complicated at a first glance, it's actually fairly easy to use: You create your message, run the pigeon command and it will put glue code to the correct places.
Read more here: https://pub.dev/packages/pigeon

Step 4: Adding the native implementation

Once you can talk between the flutter and native code, it's time to add the platform specific native implementations.
There is not much to explain here, if you have iOS background in swift/objective-c or kotlin/java you will know what to do, if not, well welcome to the club you have to fight your way through :-)

You can find the full sourcecode here: https://github.com/a1rwulf/flutter_packages/tree/feature_offline-playback

Step 5: Publishing the package and add it to your own application

For those that checked out the code, you'll see there are some hacky parts included, so instead of renaming the plugin and publishing it to https://pub.dev/ I decided to use an alternative pub.dev implementation for my usecase.

After a bit of research I decided to go for https://cloudsmith.io which turned out to be quite easy to use.
Just register and create your repository, flutter already allows usage of third party pub.dev implementations using the publish_to directive in your plugin. To reference a privately hosted plugin, there is the hosted directive.

Here is an example:
https://github.com/a1rwulf/flutter_packages/commit/705f7492efb4bbbf5be5157196a5e2ad69a683b1

If you want to give the package a try, feel free to use either video_player directly or our custom chewie version that depends on our video_player fork: https://cloudsmith.io/~seadev-studios-gmbh/repos/seadev/packages/detail/dart/chewie/1.7.2/a=noarch/

Future

Eventually I will pick up the endavour to rework the code and make an effort to upstream it to video_player or create a more offical package on pub.dev to include offline playback, but for the time being, it serves my usecase pretty well.

I hope you enjoyed the read and it will help you do your own forks of official packages.
It was my very first modification of a flutter package, so don't be afraid to take on the challenge and implement new features to existing packages, even if you cannot make it generic enough to be upstreamed right away.


Thanks for reading!
If you liked this post, please subscribe to receive more useful content.
Go to https://seadev-studios.com for more infos about our company.