Skip to content

Commit

Permalink
Support casting for Web
Browse files Browse the repository at this point in the history
  • Loading branch information
hawk23 committed Nov 27, 2024
1 parent 8a07108 commit 57677f4
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 27 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/),
and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]
- Google Cast support for Web platform.

## [0.13.0] - 2024-11-26
- Introduce platform support for web.
- Supported API calls: `loadSource(source)`, `play`, `pause`, `mute`, `unmute`, `seek(time)`, `timeShift(timeShift)`, `getCurrentTime`, `getTimeShift`, `getDuration`, `getMaxTimeShift`, `isLive`, `isPlaying`, `isAirplayActive`, `isAirplayAvailable`, `castVideo`, `castStop`, `isCastAvailable`, `isCasting`, `showAirPlayTargetPicker`, `destroy`
Expand Down
28 changes: 8 additions & 20 deletions lib/src/casting/bitmovin_cast_manager.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import 'package:bitmovin_player/bitmovin_player.dart';
import 'package:bitmovin_player/src/casting/custom_cast_message.dart';
import 'package:bitmovin_player/src/channel_manager.dart';
import 'package:bitmovin_player/src/channels.dart';
import 'package:bitmovin_player/src/methods.dart';
import 'package:bitmovin_player/src/platform/cast_manager_platform_interface.dart';

/// Singleton, providing access to GoogleCast related features.
/// Retrieve the singleton instance using [initialize].
Expand All @@ -16,8 +13,9 @@ class BitmovinCastManager implements BitmovinCastManagerApi {
/// features.
/// If no options are provided, the default options will be used.
///
/// IMPORTANT: This should only be called when the Google Cast SDK is
/// available in the application.
/// IMPORTANT: On iOS and Android, this should only be called when the
/// Google Cast SDK is available and linked in the application.
/// For Web, loading the CAST SDK is handled automatically.
static Future<BitmovinCastManager> initialize({
BitmovinCastManagerOptions options = const BitmovinCastManagerOptions(),
}) async {
Expand All @@ -30,26 +28,16 @@ class BitmovinCastManager implements BitmovinCastManagerApi {
return _singleton = instance;
}

final _mainChannel = ChannelManager.registerMethodChannel(
name: Channels.main,
);

Future<void> _initialize(BitmovinCastManagerOptions options) =>
_mainChannel.invokeMethod<void>(
Methods.castManagerInitialize,
options.toJson(),
);
CastManagerPlatformInterface.instance.initializeCastManager(options);

@override
Future<void> sendMessage({
required String message,
String? messageNamespace,
}) =>
_mainChannel.invokeMethod<void>(
Methods.castManagerSendMessage,
CustomCastMessage(
message: message,
messageNamespace: messageNamespace,
).toJson(),
CastManagerPlatformInterface.instance.sendMessage(
message: message,
messageNamespace: messageNamespace,
);
}
31 changes: 31 additions & 0 deletions lib/src/platform/cast_manager_platform_interface.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import 'package:bitmovin_player/bitmovin_player.dart';
import 'package:bitmovin_player/src/platform/cast_manager_platform_message_channel.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';

/// Platform interface for the cast manager.
abstract class CastManagerPlatformInterface extends PlatformInterface
implements BitmovinCastManagerApi {
/// Constructs a [CastManagerPlatformInterface].
CastManagerPlatformInterface() : super(token: _token);

static final Object _token = Object();

static CastManagerPlatformInterface _instance =
CastManagerPlatformMessageChannel();

/// The instance of [CastManagerPlatformInterface] to use.
///
/// Defaults to [CastManagerPlatformMessageChannel].
static CastManagerPlatformInterface get instance => _instance;

/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [CastManagerPlatformInterface]
/// when they register themselves.
static set instance(CastManagerPlatformInterface instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}

/// Initializes the cast manager with the given [options].
Future<void> initializeCastManager(BitmovinCastManagerOptions options);
}
35 changes: 35 additions & 0 deletions lib/src/platform/cast_manager_platform_message_channel.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'package:bitmovin_player/src/api/casting/bitmovin_cast_manager_options.dart';
import 'package:bitmovin_player/src/casting/custom_cast_message.dart';
import 'package:bitmovin_player/src/channel_manager.dart';
import 'package:bitmovin_player/src/channels.dart';
import 'package:bitmovin_player/src/methods.dart';
import 'package:bitmovin_player/src/platform/cast_manager_platform_interface.dart';

/// An implementation of [CastManagerPlatformInterface] that uses method
/// channels. This is currently used for iOS and Android.
class CastManagerPlatformMessageChannel extends CastManagerPlatformInterface {
final _mainChannel = ChannelManager.registerMethodChannel(
name: Channels.main,
);

@override
Future<void> sendMessage({
required String message,
String? messageNamespace,
}) =>
_mainChannel.invokeMethod<void>(
Methods.castManagerSendMessage,
CustomCastMessage(
message: message,
messageNamespace: messageNamespace,
).toJson(),
);

@override
Future<void> initializeCastManager(BitmovinCastManagerOptions options) {
return _mainChannel.invokeMethod<void>(
Methods.castManagerInitialize,
options.toJson(),
);
}
}
6 changes: 4 additions & 2 deletions lib/src/platform/web/bitmovin_player_platform_web.dart
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
// ignore: avoid_web_libraries_in_flutter
import 'dart:html';
import 'dart:ui_web' as ui;

import 'package:bitmovin_player/bitmovin_player.dart';
import 'package:bitmovin_player/src/channels.dart';
import 'package:bitmovin_player/src/platform/bitmovin_player_platform_interface.dart';
import 'package:bitmovin_player/src/platform/cast_manager_platform_interface.dart';
import 'package:bitmovin_player/src/platform/player_platform_interface.dart';
import 'package:bitmovin_player/src/platform/player_view_platform_interface.dart';
import 'package:bitmovin_player/src/platform/web/cast_manager_platform_web.dart';
import 'package:bitmovin_player/src/platform/web/player_platform_web.dart';
import 'package:bitmovin_player/src/platform/web/player_view_platform_web.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';

/// Implementation of the [BitmovinPlayerPlatformInterface] for the web
/// Implementation of the [BitmovinPlayerPlatformInterface] for the Web
/// platform.
class BitmovinPlayerPlatformWeb extends BitmovinPlayerPlatformInterface {
/// Constructs a [BitmovinPlayerPlatformWeb].
Expand All @@ -24,6 +25,7 @@ class BitmovinPlayerPlatformWeb extends BitmovinPlayerPlatformInterface {

static void registerWith(Registrar registrar) {
BitmovinPlayerPlatformInterface.instance = BitmovinPlayerPlatformWeb();
CastManagerPlatformInterface.instance = CastManagerPlatformWeb();
}

@override
Expand Down
57 changes: 57 additions & 0 deletions lib/src/platform/web/bitmovin_player_web_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class BitmovinPlayerJs {
external String getStreamType();
external void showAirplayTargetPicker();
external void on(String event, Function handler);
external void addMetadata(String metadataType, Object metadata);
}

@JS()
Expand All @@ -40,10 +41,12 @@ class PlayerConfigJs {
String? key,
PlaybackConfigJs? playback,
LicensingConfigJs? licensing,
GoogleCastRemoteControlConfigJs? remotecontrol,
});
external String? get key;
external PlaybackConfigJs? get playback;
external LicensingConfigJs? get licensing;
external GoogleCastRemoteControlConfigJs? get remotecontrol;
}

@JS()
Expand All @@ -66,6 +69,34 @@ class LicensingConfigJs {
external int? get delay;
}

@JS()
@anonymous
class GoogleCastRemoteControlConfigJs {
external factory GoogleCastRemoteControlConfigJs({
required String type,
String? receiverApplicationId,
String? messageNamespace,
});

// Factory method to allow having a default value for `type` when creating
// instances directly from Dart code.
// ignore: prefer_constructors_over_static_methods
static GoogleCastRemoteControlConfigJs create(
String? receiverApplicationId,
String? messageNamespace,
) {
return GoogleCastRemoteControlConfigJs(
type: 'googlecast',
receiverApplicationId: receiverApplicationId,
messageNamespace: messageNamespace,
);
}

external String get type;
external String? get receiverApplicationId;
external String? get messageNamespace;
}

class StreamTypeJS {
static const dash = 'dash';
static const hls = 'hls';
Expand Down Expand Up @@ -137,3 +168,29 @@ class WarningEventJs extends PlayerEventBaseJs {
external int get code;
external String? get message;
}

@JS()
@anonymous
class CastAvailableEventJs extends PlayerEventBaseJs {
external bool get receiverAvailable;
}

@JS()
@anonymous
class CastStartedEventJs extends PlayerEventBaseJs {
external String get deviceName;
external bool get resuming;
}

@JS()
@anonymous
class CastWaitingForDeviceEventJs extends PlayerEventBaseJs {
external CastPayloadJs get castPayload;
}

@JS()
@anonymous
class CastPayloadJs {
external double get currentTime;
external String? get deviceName;
}
24 changes: 24 additions & 0 deletions lib/src/platform/web/cast_manager_platform_web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'package:bitmovin_player/src/api/casting/bitmovin_cast_manager_options.dart';
import 'package:bitmovin_player/src/platform/cast_manager_platform_interface.dart';

/// An implementation of [CastManagerPlatformInterface] for the Web platform.
class CastManagerPlatformWeb extends CastManagerPlatformInterface {
BitmovinCastManagerOptions? _options;
BitmovinCastManagerOptions? get options => _options;
void Function(String)? castMessageHandler;

@override
Future<void> sendMessage({
required String message,
String? messageNamespace,
}) {
castMessageHandler?.call(message);
return Future.value();
}

@override
Future<void> initializeCastManager(BitmovinCastManagerOptions options) {
_options = options;
return Future.value();
}
}
47 changes: 46 additions & 1 deletion lib/src/platform/web/conversion.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,21 @@ extension SourceFromJs on SourceJs {
}

extension PlayerConfigToJs on PlayerConfig {
PlayerConfigJs toPlayerConfigJs() {
PlayerConfigJs toPlayerConfigJs(BitmovinCastManagerOptions? castOptions) {
GoogleCastRemoteControlConfigJs? remoteControlConfigJs;

if (castOptions != null) {
remoteControlConfigJs = GoogleCastRemoteControlConfigJs.create(
castOptions.applicationId,
castOptions.messageNamespace,
);
}

return PlayerConfigJs(
key: key,
playback: playbackConfig?.toPlaybackConfigJs(),
licensing: licensingConfig?.toLicensingConfigJs(),
remotecontrol: remoteControlConfigJs,
);
}
}
Expand Down Expand Up @@ -108,6 +118,14 @@ extension PlayerEventBaseConversion on PlayerEventBaseJs {
SourceUnloadedEvent toSourceUnloadedEvent() {
return SourceUnloadedEvent(timestamp: timestamp);
}

CastStartEvent toCastStartEvent() {
return CastStartEvent(timestamp: timestamp);
}

CastStoppedEvent toCastStoppedEvent() {
return CastStoppedEvent(timestamp: timestamp);
}
}

extension UserInteractionEventConversion on UserInteractionEventJs {
Expand Down Expand Up @@ -177,3 +195,30 @@ extension WarningEventConversion on WarningEventJs {
);
}
}

extension CastAvailableEventConversion on CastAvailableEventJs {
CastAvailableEvent toCastAvailableEvent() {
return CastAvailableEvent(timestamp: timestamp);
}
}

extension CastStartedEventConversion on CastStartedEventJs {
CastStartedEvent toCastStartedEvent() {
return CastStartedEvent(
timestamp: timestamp,
deviceName: deviceName,
);
}
}

extension CastWaitingForDeviceEventConversion on CastWaitingForDeviceEventJs {
CastWaitingForDeviceEvent toCastWaitingForDeviceEvent() {
return CastWaitingForDeviceEvent(
castPayload: CastPayload(
currentTime: castPayload.currentTime,
deviceName: castPayload.deviceName,
),
timestamp: timestamp,
);
}
}
11 changes: 9 additions & 2 deletions lib/src/platform/web/player_platform_web.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import 'dart:async';

import 'package:bitmovin_player/bitmovin_player.dart';
import 'package:bitmovin_player/src/platform/cast_manager_platform_interface.dart';
import 'package:bitmovin_player/src/platform/player_platform_interface.dart';
import 'package:bitmovin_player/src/platform/web/bitmovin_player_web_api.dart';
import 'package:bitmovin_player/src/platform/web/cast_manager_platform_web.dart';
import 'package:bitmovin_player/src/platform/web/conversion.dart';
import 'package:bitmovin_player/src/platform/web/player_web_event_handler.dart';
import 'package:web/web.dart' as web;

/// An implementation of [PlayerPlatformInterface] for the web platform.
/// An implementation of [PlayerPlatformInterface] for the Web platform.
/// This is specific to a single player instance.
class PlayerPlatformWeb extends PlayerPlatformInterface {
PlayerPlatformWeb(
Expand All @@ -17,10 +19,13 @@ class PlayerPlatformWeb extends PlayerPlatformInterface {
) {
_player = BitmovinPlayerJs(
_createContainer(),
config.toPlayerConfigJs(),
config.toPlayerConfigJs(castManager.options),
);

_playerEventHandler = PlayerWebEventHandler(_player, _onPlatformEvent);
castManager.castMessageHandler = (String message) {
_player.addMetadata('CAST', message);
};
}

/// Unique identifier for this player instance.
Expand All @@ -30,6 +35,8 @@ class PlayerPlatformWeb extends PlayerPlatformInterface {
late BitmovinPlayerJs _player;
// ignore: unused_field
late PlayerWebEventHandler _playerEventHandler;
CastManagerPlatformWeb get castManager =>
CastManagerPlatformInterface.instance as CastManagerPlatformWeb;

web.Element _createContainer() {
final div = web.document.createElement('div') as web.HTMLDivElement
Expand Down
2 changes: 1 addition & 1 deletion lib/src/platform/web/player_view_platform_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'package:bitmovin_player/src/platform.dart';
import 'package:bitmovin_player/src/platform/player_view_platform_interface.dart';
import 'package:flutter/widgets.dart';

/// An implementation of [PlayerViewPlatformInterface] for the web platform.
/// An implementation of [PlayerViewPlatformInterface] for the Web platform.
/// This is specific to a single player view instance.
class PlayerViewPlatformWeb extends PlayerViewPlatformInterface {
PlayerViewPlatformWeb(
Expand Down
Loading

0 comments on commit 57677f4

Please sign in to comment.