Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support casting for Web #199

Merged
merged 8 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ 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
- Support for `Player.isPaused` and `Player.isMuted`

## [0.13.0] - 2024-11-26
- Introduce platform support for web.
- 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`
- Supported events: `play`,`playing`,`paused`,`timeChanged`,`seek`,`seeked`,`timeShift`,`timeShifted`,`playbackFinished`,`error`,`muted`,`unmuted`,`warning`,`ready`,`sourceLoaded`,`sourceUnloaded`
- Update Bitmovin's native Android Player SDK version to `3.94.0`
Expand All @@ -20,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
- Update Bitmovin's native iOS Player SDK version to `3.77.0`

### Removed
- Custom spec source `https://github.com/bitmovin/cocoapod-specs.git` from `example/ios/Podfile` as `BitmovinPlayer` is now published to the public CocoaPods registry.
- Custom spec source `https://github.com/bitmovin/cocoapod-specs.git` from `example/ios/Podfile` as `BitmovinPlayer` is now published to the public CocoaPods registry

## [0.11.0] - 2024-09-11
### Changed
Expand Down
3 changes: 3 additions & 0 deletions lib/src/api/casting/bitmovin_cast_manager_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import 'package:bitmovin_player/bitmovin_player.dart';
// ignore: one_member_abstracts
abstract class BitmovinCastManagerApi {
/// Sends the given `message` to the cast receiver.
/// On Android and iOS an optional `messageNamespace` can be provided on which
/// the message should be sent. Should there be a `messageNamespace` provided
/// on Web, it will be ignored.
Future<void> sendMessage({
required String message,
String? messageNamespace,
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 @@ -33,6 +33,7 @@ class BitmovinPlayerJs {
external String getStreamType();
external void showAirplayTargetPicker();
external void on(String event, Function handler);
external bool addMetadata(String metadataType, Object metadata);
}

@JS()
Expand All @@ -42,10 +43,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 @@ -68,6 +71,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 @@ -139,3 +170,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,
);
}
}
Loading