Refactor classes and improve docs
This commit is contained in:
parent
97425c97f6
commit
d31ff882b5
14 changed files with 246 additions and 105 deletions
23
.vscode/launch.json
vendored
23
.vscode/launch.json
vendored
|
|
@ -5,9 +5,30 @@
|
|||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Flutter",
|
||||
"name": "Current Device",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "Android",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"deviceId": "android"
|
||||
},
|
||||
{
|
||||
"name": "iPhone",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"deviceId": "iphone"
|
||||
},
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "All Devices",
|
||||
"configurations": [
|
||||
"Android",
|
||||
"iPhone"
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
13
Makefile
13
Makefile
|
|
@ -1,6 +1,8 @@
|
|||
.PHONY: pigeon deploy-receiver open-android open-ios run-all
|
||||
|
||||
pigeon: # Generates the typesafe bridge between host and flutter
|
||||
flutter pub run pigeon \
|
||||
--input lib/src/PlatformBridgeApisDefinition.dart \
|
||||
--input pigeon/PlatformBridgeApisDefinition.dart \
|
||||
--dart_out lib/src/PlatformBridgeApis.dart \
|
||||
--objc_header_out ios/Classes/PlatformBridgeApis.h \
|
||||
--objc_source_out ios/Classes/PlatformBridgeApis.m \
|
||||
|
|
@ -9,3 +11,12 @@ pigeon: # Generates the typesafe bridge between host and flutter
|
|||
|
||||
deploy-receiver:
|
||||
surge receiver
|
||||
|
||||
open-android:
|
||||
studio example/android
|
||||
|
||||
open-ios:
|
||||
open example/ios/Runner.xcworkspace
|
||||
|
||||
run-all:
|
||||
cd example && flutter run -d all
|
||||
13
README.md
13
README.md
|
|
@ -12,6 +12,9 @@ Currently only the following APIs are integrated (both Android and iOS):
|
|||
* Session state
|
||||
* Send custom message
|
||||
* Listen to received custom messages
|
||||
* Load RemoteMediaRequestData
|
||||
* Play, Pause, Stop media
|
||||
* Expanded controls
|
||||
* Cast Button
|
||||
* Chromecast connection
|
||||
|
||||
|
|
@ -137,3 +140,13 @@ I used this project to test the capabilities of the following technologies:
|
|||
* Chromecast API (Sender - Android SDK)
|
||||
* Flutter
|
||||
* Flutter custom platform-specific code
|
||||
|
||||
## Roadmap
|
||||
|
||||
* Volume in Expanded Controls
|
||||
* Currently connected cast device name
|
||||
* CC in Expanded Controls
|
||||
* Handle Ad Break
|
||||
* Handle progress seek
|
||||
* Handle queue
|
||||
* Handle mini-player
|
||||
|
|
|
|||
|
|
@ -32,18 +32,27 @@ class _MyAppState extends State<MyApp> {
|
|||
super.initState();
|
||||
castFramework = FlutterCastFramework.create([castNamespace]);
|
||||
castFramework.castContext.state.addListener(_onCastStateChanged);
|
||||
castFramework.castContext.sessionManager.state
|
||||
.addListener(_onSessionStateChanged);
|
||||
castFramework.castContext.sessionManager.onMessageReceived =
|
||||
_onMessageReceived;
|
||||
castFramework.castContext.sessionManager.onStatusUpdated =
|
||||
_onRemoteMediaClientStatusUpdated;
|
||||
|
||||
final sessionManager = castFramework.castContext.sessionManager;
|
||||
sessionManager.state.addListener(_onSessionStateChanged);
|
||||
sessionManager.onMessageReceived = _onMessageReceived;
|
||||
sessionManager.remoteMediaClient.playerState
|
||||
.addListener(_onRemoteMediaClientStatusUpdated);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// Clean up the controller when the widget is disposed.
|
||||
textMessageController.dispose();
|
||||
|
||||
castFramework.castContext.state.removeListener(_onCastStateChanged);
|
||||
|
||||
final sessionManager = castFramework.castContext.sessionManager;
|
||||
sessionManager.state.removeListener(_onSessionStateChanged);
|
||||
sessionManager.onMessageReceived = null;
|
||||
sessionManager.remoteMediaClient.playerState
|
||||
.removeListener(_onRemoteMediaClientStatusUpdated);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -68,7 +77,9 @@ class _MyAppState extends State<MyApp> {
|
|||
});
|
||||
}
|
||||
|
||||
void _onRemoteMediaClientStatusUpdated(PlayerState playerState) {
|
||||
void _onRemoteMediaClientStatusUpdated() {
|
||||
final playerState = castFramework
|
||||
.castContext.sessionManager.remoteMediaClient.playerState.value;
|
||||
debugPrint("RemoteMediaClient status updated - playerState $playerState");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@ library cast;
|
|||
|
||||
export 'src/cast/CastContext.dart';
|
||||
export 'src/cast/SessionManager.dart';
|
||||
export 'src/cast/RemoteMediaClient.dart';
|
||||
export 'src/flutter_cast_framework.dart';
|
||||
export 'src/PlatformBridgeApis.dart';
|
||||
export 'src/PlatformBridgeApis.dart' hide CastFlutterApi, CastHostApi;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,33 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../PlatformBridgeApis.dart';
|
||||
import 'SessionManager.dart';
|
||||
|
||||
/// Class wrapping the global context fot the Cast SDK
|
||||
class CastContext {
|
||||
final ValueNotifier<CastState> state = ValueNotifier(CastState.unavailable);
|
||||
final CastHostApi _hostApi;
|
||||
|
||||
CastContext(this._hostApi);
|
||||
|
||||
final CastHostApi _hostApi;
|
||||
|
||||
/// Listenable connection state of the cast device
|
||||
ValueListenable<CastState> get state => _stateNotifier;
|
||||
final _stateNotifier = ValueNotifier(CastState.unavailable);
|
||||
|
||||
/// Display the native dialog to select the cast device to connect
|
||||
void showCastChooserDialog() {
|
||||
_hostApi.showCastDialog();
|
||||
}
|
||||
|
||||
/// Internal method that shouldn't be visible
|
||||
@internal
|
||||
void onCastStateChanged(int castState) {
|
||||
state.value = CastState.values[castState];
|
||||
_stateNotifier.value = CastState.values[castState];
|
||||
}
|
||||
|
||||
SessionManager? _sessionManager;
|
||||
|
||||
/// Returns the SessionManager.
|
||||
SessionManager get sessionManager {
|
||||
var result = _sessionManager;
|
||||
if (result == null) {
|
||||
|
|
@ -26,10 +37,16 @@ class CastContext {
|
|||
}
|
||||
}
|
||||
|
||||
/// The possible casting states.
|
||||
enum CastState {
|
||||
/// Cast connection has never been initialized.
|
||||
idle, // 0
|
||||
/// No Cast devices are available.
|
||||
unavailable, // 1
|
||||
/// Cast devices are available, but a Cast session is not established.
|
||||
unconnected, // 2
|
||||
/// A Cast session is being established.
|
||||
connecting, // 3
|
||||
/// A Cast session is established.
|
||||
connected, // 4
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,88 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../../cast.dart';
|
||||
import '../PlatformBridgeApis.dart';
|
||||
|
||||
typedef ProgressListener = void Function(int progressMs, int durationMs);
|
||||
|
||||
/// Class for controlling a media player application running on a receiver.
|
||||
class RemoteMediaClient {
|
||||
final CastHostApi _hostApi;
|
||||
|
||||
ProgressListener? onProgressUpdated;
|
||||
|
||||
RemoteMediaClient(this._hostApi);
|
||||
|
||||
final CastHostApi _hostApi;
|
||||
|
||||
/// Listenable state of the remote media player
|
||||
ValueListenable<PlayerState> get playerState => _playerStateNotifier;
|
||||
final _playerStateNotifier = ValueNotifier(PlayerState.unknown);
|
||||
|
||||
/// Callback to get updates on the progress of the currently playing media.
|
||||
ProgressListener? onProgressUpdated;
|
||||
|
||||
/// Called when updated media metadata is received.
|
||||
VoidCallback? onMetadataUpdated;
|
||||
|
||||
/// Called when updated player queue status information is received.
|
||||
VoidCallback? onQueueStatusUpdated;
|
||||
|
||||
/// Called when updated player queue preload status information is received,
|
||||
/// for example, the next item to play has been preloaded.
|
||||
VoidCallback? onPreloadStatusUpdated;
|
||||
|
||||
/// Called when there is an outgoing request to the receiver.
|
||||
VoidCallback? onSendingRemoteMediaRequest;
|
||||
|
||||
/// Called when updated ad break status information is received.
|
||||
VoidCallback? onAdBreakStatusUpdated;
|
||||
|
||||
/// Called when receiving media error message.
|
||||
VoidCallback? onMediaError;
|
||||
|
||||
/// Loads a new media item with specified options.
|
||||
void load(MediaLoadRequestData request) {
|
||||
_hostApi.loadMediaLoadRequestData(request);
|
||||
}
|
||||
|
||||
/// Begins (or resumes) playback of the current media item.
|
||||
void play() {
|
||||
_hostApi.play();
|
||||
}
|
||||
|
||||
/// Pauses playback of the current media item.
|
||||
void pause() {
|
||||
_hostApi.pause();
|
||||
}
|
||||
|
||||
/// Stops playback of the current media item.
|
||||
void stop() {
|
||||
_hostApi.stop();
|
||||
}
|
||||
|
||||
/// Returns the current media information
|
||||
Future<MediaInfo> getMediaInfo() async {
|
||||
// FIXME: can remove future? we could avoid to call host and rely on listener callbacks (maybe onMetadataUpdated)
|
||||
return await _hostApi.getMediaInfo();
|
||||
}
|
||||
|
||||
/// Internal method that shouldn't be visible
|
||||
@internal
|
||||
void dispatchPlayerStateUpdate(PlayerState playerState) {
|
||||
this._playerStateNotifier.value = playerState;
|
||||
}
|
||||
}
|
||||
|
||||
/// State of the remote media player
|
||||
enum PlayerState {
|
||||
/// Constant indicating unknown player state.
|
||||
unknown, // 0
|
||||
/// Constant indicating that the media player is idle.
|
||||
idle, // 1
|
||||
/// Constant indicating that the media player is playing.
|
||||
playing, // 2
|
||||
/// Constant indicating that the media player is paused.
|
||||
paused, // 3
|
||||
/// Constant indicating that the media player is buffering.
|
||||
buffering, // 4
|
||||
/// Constant indicating that the media player is loading.
|
||||
loading, // 5
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,33 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import '../PlatformBridgeApis.dart';
|
||||
import 'RemoteMediaClient.dart';
|
||||
|
||||
/// A class that manages Session instances. The application can attach a
|
||||
/// listeners to be notified of session events.
|
||||
class SessionManager {
|
||||
final CastHostApi _hostApi;
|
||||
|
||||
SessionManager(this._hostApi);
|
||||
|
||||
final state = ValueNotifier(SessionState.idle);
|
||||
final playerState = ValueNotifier(PlayerState.unknown);
|
||||
final CastHostApi _hostApi;
|
||||
|
||||
/// Listenable session state of the cast device
|
||||
ValueListenable<SessionState> get state => _stateNotifier;
|
||||
final _stateNotifier = ValueNotifier(SessionState.idle);
|
||||
|
||||
/// Internal method that shouldn't be visible
|
||||
@internal
|
||||
void onSessionStateChanged(SessionState sessionState) {
|
||||
switch (sessionState) {
|
||||
case SessionState.session_starting:
|
||||
case SessionState.session_started:
|
||||
case SessionState.session_start_failed:
|
||||
case SessionState.session_ending:
|
||||
case SessionState.session_ended:
|
||||
case SessionState.session_resuming:
|
||||
case SessionState.session_resumed:
|
||||
case SessionState.session_resume_failed:
|
||||
case SessionState.session_suspended:
|
||||
state.value = sessionState;
|
||||
case SessionState.starting:
|
||||
case SessionState.started:
|
||||
case SessionState.start_failed:
|
||||
case SessionState.ending:
|
||||
case SessionState.ended:
|
||||
case SessionState.resuming:
|
||||
case SessionState.resumed:
|
||||
case SessionState.resume_failed:
|
||||
case SessionState.suspended:
|
||||
_stateNotifier.value = sessionState;
|
||||
break;
|
||||
case SessionState.idle:
|
||||
// Not raised
|
||||
|
|
@ -29,21 +35,11 @@ class SessionManager {
|
|||
}
|
||||
}
|
||||
|
||||
void dispatchOnPlayerStateUpdated(PlayerState playerState) {
|
||||
this.playerState.value = playerState;
|
||||
onStatusUpdated?.call(playerState);
|
||||
}
|
||||
|
||||
/// Callback called when the Cast Receiver sent a message
|
||||
MessageReceivedCallback? onMessageReceived;
|
||||
|
||||
StatusUpdatedCallback? onStatusUpdated;
|
||||
VoidCallback? onMetadataUpdated;
|
||||
VoidCallback? onQueueStatusUpdated;
|
||||
VoidCallback? onPreloadStatusUpdated;
|
||||
VoidCallback? onSendingRemoteMediaRequest;
|
||||
VoidCallback? onAdBreakStatusUpdated;
|
||||
VoidCallback? onMediaError;
|
||||
|
||||
/// Internal method that shouldn't be visible
|
||||
@internal
|
||||
void platformOnMessageReceived(CastMessage castMessage) {
|
||||
var thisOnMessageReceived = onMessageReceived;
|
||||
|
||||
|
|
@ -54,6 +50,7 @@ class SessionManager {
|
|||
thisOnMessageReceived(namespace, message);
|
||||
}
|
||||
|
||||
/// Send a string message to the Cast Receiver using the input namespace
|
||||
void sendMessage(String namespace, String message) {
|
||||
final castMessage = CastMessage();
|
||||
castMessage.namespace = namespace;
|
||||
|
|
@ -62,6 +59,8 @@ class SessionManager {
|
|||
}
|
||||
|
||||
RemoteMediaClient? _remoteMediaClient;
|
||||
|
||||
/// Returns the RemoteMediaClient for remote media control.
|
||||
RemoteMediaClient get remoteMediaClient {
|
||||
var result = _remoteMediaClient;
|
||||
if (result == null) {
|
||||
|
|
@ -74,26 +73,16 @@ class SessionManager {
|
|||
typedef MessageReceivedCallback = void Function(
|
||||
String namespace, String message);
|
||||
|
||||
/// State of the session
|
||||
enum SessionState {
|
||||
idle,
|
||||
session_starting,
|
||||
session_started,
|
||||
session_start_failed,
|
||||
session_ending,
|
||||
session_ended,
|
||||
session_resuming,
|
||||
session_resumed,
|
||||
session_resume_failed,
|
||||
session_suspended,
|
||||
}
|
||||
|
||||
typedef StatusUpdatedCallback = void Function(PlayerState);
|
||||
|
||||
enum PlayerState {
|
||||
unknown, // 0
|
||||
idle, // 1
|
||||
playing, // 2
|
||||
paused, // 3
|
||||
buffering, // 4
|
||||
loading, // 5
|
||||
starting,
|
||||
started,
|
||||
start_failed,
|
||||
ending,
|
||||
ended,
|
||||
resuming,
|
||||
resumed,
|
||||
resume_failed,
|
||||
suspended,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ class ExpandedControlsPlayer extends StatelessWidget {
|
|||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 36, vertical: 0),
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: sessionManager.playerState,
|
||||
valueListenable: sessionManager.remoteMediaClient.playerState,
|
||||
builder: (context, value, child) {
|
||||
final playerState = value as PlayerState;
|
||||
return Row(
|
||||
|
|
|
|||
|
|
@ -1,24 +1,51 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_cast_framework/cast.dart';
|
||||
import 'package:flutter_cast_framework/src/cast/RemoteMediaClient.dart';
|
||||
|
||||
import 'PlatformBridgeApis.dart';
|
||||
import 'cast/CastContext.dart';
|
||||
|
||||
class FlutterCastFramework extends CastFlutterApi {
|
||||
/// Entrypoint for the Flutter Cast Framework
|
||||
class FlutterCastFramework {
|
||||
final _hostApi = CastHostApi();
|
||||
late CastContext castContext;
|
||||
late _CastFlutterApiImplementor _castFlutterApiImplementor;
|
||||
|
||||
/// List of namespaces to listen for custom messages
|
||||
late List<String> namespaces = [];
|
||||
/// Get the entrypoint for the Cast SDK. This is immutable and it is expected to never change.
|
||||
CastContext get castContext => _castFlutterApiImplementor.castContext;
|
||||
|
||||
/// Create the Flutter Cast Framework.
|
||||
/// namespaces is the list of namespaces to listen for custom messages.
|
||||
FlutterCastFramework.create(List<String> namespaces) {
|
||||
debugPrint("FlutterCastFramework created!");
|
||||
this.namespaces = namespaces;
|
||||
this.castContext = CastContext(_hostApi);
|
||||
CastFlutterApi.setup(this);
|
||||
final castContext = CastContext(_hostApi);
|
||||
this._castFlutterApiImplementor = new _CastFlutterApiImplementor(
|
||||
castContext: castContext,
|
||||
namespaces: namespaces,
|
||||
);
|
||||
|
||||
CastFlutterApi.setup(this._castFlutterApiImplementor);
|
||||
}
|
||||
}
|
||||
|
||||
/// This implements Pigeon's API called by the Host platform. This is implemented
|
||||
/// in a separate class to hide the methods
|
||||
class _CastFlutterApiImplementor extends CastFlutterApi {
|
||||
final CastContext castContext;
|
||||
final List<String> namespaces = [];
|
||||
|
||||
SessionManager get sessionManager => castContext.sessionManager;
|
||||
RemoteMediaClient get remoteMediaClient =>
|
||||
castContext.sessionManager.remoteMediaClient;
|
||||
|
||||
_CastFlutterApiImplementor({
|
||||
required this.castContext,
|
||||
List<String>? namespaces,
|
||||
}) {
|
||||
if (namespaces != null) {
|
||||
this.namespaces.addAll(namespaces);
|
||||
}
|
||||
}
|
||||
|
||||
//region CastFlutterApi implementation
|
||||
@override
|
||||
List<String?> getSessionMessageNamespaces() {
|
||||
return namespaces;
|
||||
|
|
@ -31,106 +58,96 @@ class FlutterCastFramework extends CastFlutterApi {
|
|||
|
||||
@override
|
||||
void onMessageReceived(CastMessage castMessage) {
|
||||
castContext.sessionManager.platformOnMessageReceived(castMessage);
|
||||
sessionManager.platformOnMessageReceived(castMessage);
|
||||
}
|
||||
|
||||
//region Session State handling
|
||||
@override
|
||||
void onSessionEnded() {
|
||||
castContext.sessionManager
|
||||
.onSessionStateChanged(SessionState.session_ended);
|
||||
sessionManager.onSessionStateChanged(SessionState.ended);
|
||||
}
|
||||
|
||||
@override
|
||||
void onSessionEnding() {
|
||||
castContext.sessionManager
|
||||
.onSessionStateChanged(SessionState.session_ending);
|
||||
sessionManager.onSessionStateChanged(SessionState.ending);
|
||||
}
|
||||
|
||||
@override
|
||||
void onSessionResumeFailed() {
|
||||
castContext.sessionManager
|
||||
.onSessionStateChanged(SessionState.session_resume_failed);
|
||||
sessionManager.onSessionStateChanged(SessionState.resume_failed);
|
||||
}
|
||||
|
||||
@override
|
||||
void onSessionResumed() {
|
||||
castContext.sessionManager
|
||||
.onSessionStateChanged(SessionState.session_resumed);
|
||||
sessionManager.onSessionStateChanged(SessionState.resumed);
|
||||
}
|
||||
|
||||
@override
|
||||
void onSessionResuming() {
|
||||
castContext.sessionManager
|
||||
.onSessionStateChanged(SessionState.session_resuming);
|
||||
sessionManager.onSessionStateChanged(SessionState.resuming);
|
||||
}
|
||||
|
||||
@override
|
||||
void onSessionStartFailed() {
|
||||
castContext.sessionManager
|
||||
.onSessionStateChanged(SessionState.session_start_failed);
|
||||
sessionManager.onSessionStateChanged(SessionState.start_failed);
|
||||
}
|
||||
|
||||
@override
|
||||
void onSessionStarted() {
|
||||
castContext.sessionManager
|
||||
.onSessionStateChanged(SessionState.session_started);
|
||||
sessionManager.onSessionStateChanged(SessionState.started);
|
||||
}
|
||||
|
||||
@override
|
||||
void onSessionStarting() {
|
||||
castContext.sessionManager
|
||||
.onSessionStateChanged(SessionState.session_starting);
|
||||
sessionManager.onSessionStateChanged(SessionState.starting);
|
||||
}
|
||||
|
||||
@override
|
||||
void onSessionSuspended() {
|
||||
castContext.sessionManager
|
||||
.onSessionStateChanged(SessionState.session_suspended);
|
||||
sessionManager.onSessionStateChanged(SessionState.suspended);
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region RemoteMediaClient
|
||||
@override
|
||||
void onAdBreakStatusUpdated() {
|
||||
castContext.sessionManager.onAdBreakStatusUpdated?.call();
|
||||
remoteMediaClient.onAdBreakStatusUpdated?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
void onMediaError() {
|
||||
castContext.sessionManager.onMediaError?.call();
|
||||
remoteMediaClient.onMediaError?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
void onMetadataUpdated() {
|
||||
castContext.sessionManager.onMetadataUpdated?.call();
|
||||
remoteMediaClient.onMetadataUpdated?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
void onPreloadStatusUpdated() {
|
||||
castContext.sessionManager.onPreloadStatusUpdated?.call();
|
||||
remoteMediaClient.onPreloadStatusUpdated?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
void onQueueStatusUpdated() {
|
||||
castContext.sessionManager.onQueueStatusUpdated?.call();
|
||||
remoteMediaClient.onQueueStatusUpdated?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
void onSendingRemoteMediaRequest() {
|
||||
castContext.sessionManager.onSendingRemoteMediaRequest?.call();
|
||||
remoteMediaClient.onSendingRemoteMediaRequest?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
void onStatusUpdated(int playerStateRaw) {
|
||||
final playerState = PlayerState.values[playerStateRaw];
|
||||
castContext.sessionManager.dispatchOnPlayerStateUpdated(playerState);
|
||||
remoteMediaClient.dispatchPlayerStateUpdate(playerState);
|
||||
}
|
||||
|
||||
@override
|
||||
void onProgressUpdated(int progressMs, int durationMs) {
|
||||
castContext.sessionManager.remoteMediaClient.onProgressUpdated
|
||||
?.call(progressMs, durationMs);
|
||||
remoteMediaClient.onProgressUpdated?.call(progressMs, durationMs);
|
||||
}
|
||||
//endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,3 +3,6 @@ library widgets;
|
|||
export 'src/cast/widgets/CastButton.dart';
|
||||
export 'src/cast/widgets/CastIcon.dart';
|
||||
export 'src/cast/widgets/expanded_controls/ExpandedControls.dart';
|
||||
export 'src/cast/widgets/expanded_controls/ExpandedControlsPlayer.dart';
|
||||
export 'src/cast/widgets/expanded_controls/ExpandedControlsProgress.dart';
|
||||
export 'src/cast/widgets/expanded_controls/ExpandedControlsToolbar.dart';
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ packages:
|
|||
source: hosted
|
||||
version: "0.12.10"
|
||||
meta:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: meta
|
||||
url: "https://pub.dartlang.org"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ dependencies:
|
|||
flutter:
|
||||
sdk: flutter
|
||||
flutter_svg: ^0.22.0
|
||||
meta: ^1.7.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
|||
Loading…
Reference in a new issue