bccm_player 0.2.3-alpha.1 bccm_player: ^0.2.3-alpha.1 copied to clipboard
ExoPlayer/AVPlayer via platform views, with cast, PiP, background audio, audio selection, etc.
BccmPlayer - a flutter video player package #
Note: This was recently published so documentation may be lacking, but we want this to work for others, so create an issue on github if you need help.
This is a video player primarily designed for video-heavy apps that need features like background playback, PiP, casting, analytics, etc.
Used by the open source apps Bible Kids and BCC Media. (source code here).
Difference from video_player/chewie/betterplayer, etc. #
A major difference is that BccmPlayer uses hybrid composition platform views to display the video instead of textures. This means the video is rendered in the native view hierarchy without any intermediate steps, which has several benefits:
- Native video performance
- Subtitles are rendered by the native player (avplayer/exoplayer)
- Can use native controls (
useNativeControls
on VideoPlayerView)
Platforms #
- ✅ iOS
- ✅ Android
- [-] Web (partial/hacky)
Features #
- ✅ Native video via hybrid composition
- ✅ HLS, DASH, MP4 (anything exoplayer and avplayer supports)
- ✅ Chromecast
- ✅ Background playback
- ✅ Picture in picture
- ✅ Notification center
- ✅ Audio track selection
- ✅ Subtitle track selection
- ✅ Fullscreen
- ✅ NPAW/Youbora analytics
- ✅ Metadata
- ✅ HDR content (see HDR)
Installation #
-
Add the dependency
flutter pub add bccm_player
-
(Android) For native fullscreen and picture-in-picture to work correctly, you need to override these 2 methods on your MainActivity:
class MainActivity : FlutterFragmentActivity() { @SuppressLint("MissingSuperCall") override fun onPictureInPictureModeChanged( isInPictureInPictureMode: Boolean, newConfig: Configuration? ) { super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) // This is important for PiP to behave correctly (e.g. pause video when exiting PiP). val bccmPlayer = flutterEngine?.plugins?.get(BccmPlayerPlugin::class.javaObjectType) as BccmPlayerPlugin? bccmPlayer?.handleOnPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) } override fun onBackPressed() { // This makes the back button work correctly in the native fullscreen player. // Returns true if the event was handled. if (!BccmPlayerPlugin.handleOnBackPressed(this)) { super.onBackPressed() } } }
-
(Android) Add
supportsPictureInPicture="true"
to your AndroidManifest for MainActivity:
<activity
android:name=".MainActivity"
android:supportsPictureInPicture="true"
...
Usage #
Initialize #
This is necessary to ensure some of the more advanced features work smoothly. Add this to your main.dart before runApp().
await BccmPlayerInterface.instance.setup(); // don't worry, it completes in milliseconds.
Basic usage #
Note: This is mostly to explain how things work. Even for simple use cases, I recommend skipping straight to the Advanced Usage section.
// Create a player
final playerId = await BccmPlayerInterface.instance.newPlayer();
// Play something
await BccmPlayerInterface.instance.replaceCurrentMediaItem(
playerId,
MediaItem(
url: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
mimeType: 'video/mp4',
metadata: MediaMetadata(title: 'Bick Buck Bunny (MP4)'),
),
);
// Show it via a widget
@override
Widget build(BuildContext context, WidgetRef ref) {
return VideoPlayerView(playerId: playerId);
}
Use the widgets:
- VideoPlayerView(): The normal widget for displaying a video with controls.
- VideoPlatformView(): A raw video widget
- BccmCastButton(): A button to connect to cast-enabled devices
- MiniPlayer(): A skeleton widget included for convenience.
Advanced usage #
Primary player #
As this plugin is designed for a VOD-type application, the plugin has the concept of a "primary" player. The plugin guarantees a primary player during initialization. This makes it easy for flutter to know which player to use by default across your app. The primary player also has some extra superpowers:
- it controls what's shown in the notification center
- it automatically transfers the current video to chromecasts when you start a session (technical details here).
- cast sessions will automatically claim the primaryPlayer (so you don't need extra logic for handling the cast sessions)
The primaryPlayerId is available via the StateNotifier at BccmPlayerInterface.instance.stateNotifier. Example usage with our builtin riverpod integration:
final primaryPlayerState = ref.watch(primaryPlayerProvider);
final widget = VideoPlayerView(playerId: primaryPlayerState.id);
debugPrint('Currently playing: ${primaryPlayerState.currentMediaItem?.metadata?.title}');
Configure default languages #
BccmPlayerInterface.instance.setAppConfig(
AppConfig(
appLanguage: appLanguage.languageCode, // 2-letter IETF BCP 47 code
audioLanguage: audioLanguage, // 2-letter IETF BCP 47 code
subtitleLanguage: subtitleLanguage, // 2-letter IETF BCP 47 code
analyticsId: analyticsId, // Can be used by analytics services like NPAW
sessionId: sessionId, // Can be used by analytics services like NPAW
),
)
Custom controls #
This is designed to be possible but it's not possible yet. If anyone needs this I can make it possible very quickly so just create an issue and tag @andreasgangso.
HDR content, SurfaceViews #
For this to work on Android you need to enable SurfaceViews. However, the reason this isn't enabled by default is because flutter has a bug with surface views. Check out this issue on the flutter repo.
You can opt-in to using surfaceViews via the useSurfaceView
property on VideoPlayerView or VideoPlatformView:
VideoPlayerView(
id: player.playerId,
useSurfaceView: true,
),
Chromecast #
Casting requires some extra steps to setup.
-
You need a receiver app ID. Recommend starting with the receiver code at https://github.com/bcc-code/bccm-player-chromecast, as it includes support for default languages. That receiver is also available at this appId for testing:
519C9F80
. -
(Android) Change your android FlutterActivity to be a FlutterFragmentActivity (required for the native chromecast views):
// android/app/src/main/kotlin/your/bundle/name/MainActivity.kt - class MainActivity : FlutterActivity() { + class MainActivity : FlutterFragmentActivity() {
-
(Android) Add a values.xml with your own receiver id:
<string name="cast_app_id">ABCD1234</string>
-
(Android) Update
NormalTheme
in yourstyles.xml
to use an AppCompat theme and have acolorPrimary
. This is a requirement from the Cast SDK.<style name="NormalTheme" parent="@style/Theme.AppCompat.Light.NoActionBar"> <!-- Change to use "AppCompat" --> <item name="android:windowBackground">?android:colorBackground</item> <!-- This was already there --> <item name="colorPrimary">#ffffffff</item> <!-- Added --> </style>
-
(Android) Update
NormalTheme
in yournight/styles.xml
too:<style name="NormalTheme" parent="@style/Theme.AppCompat.NoActionBar"> <!-- Note there's no "Light" --> <item name="android:windowBackground">?android:colorBackground</item> <item name="colorPrimary">#ffffffff</item> <!-- Added --> </style>
-
(iOS) Follow the cast sdk documentation on how to add the "NSBonjourServices" and "NSLocalNetworkUsageDescription" plist values: https://developers.google.com/cast/docs/ios_sender#ios_14
-
(iOS) Add your receiver id to your Info.plist:
<key>cast_app_id</key> <string>ABCD1234</string>
-
(iOS) Example Info.plist for step 4 and 5:
<key>cast_app_id</key> <string>519C9F80</string> <key>NSLocalNetworkUsageDescription</key> <string>${PRODUCT_NAME} uses the local network to discover Cast-enabled devices on your WiFi network.</string> <key>NSBonjourServices</key> <array> <string>_519C9F80._googlecast._tcp</string> <string>_googlecast._tcp</string> </array>
Plugins #
Riverpod #
The riverpod providers are there to simplify usage of the StateNotifiers and event streams. See ./lib/src/plugins/riverpod/providers to find available providers.
final player = ref.watch(primaryPlayerProvider); // should never be null when the plugin is initialized
VideoPlayerView(id: player.playerId);
Npaw / Youbora #
NPAW can be enabled with "setNpawConfig()":
BccmPlayerInterface.instance.setNpawConfig(
NpawConfig(
accountCode: '',
appName: '',
),
)
It uses title etc from your MediaMetadata by default, but you can customize it via extras
.
Currently limited to the following properties:
MediaMetadata(
extras: {
'npaw.content.id': '123',
'npaw.content.title': 'Live',
'npaw.content.tvShow': 'Show',
'npaw.content.season': 'Season',
'npaw.content.episodeTitle': 'Livestream',
'npaw.content.isLive': 'true',
},
);
For BCC Media apps #
Add the BCC Media playback listener (sends episode progress to API and that kind of stuff). Add it in main.dart.
BccmPlayerInterface.instance.addPlaybackListener(
BccmPlaybackListener(ref: ref, apiProvider: apiProvider),
)
Contributing #
See CONTRIBUTING.md