Add playback event stream to platform interface.

This commit is contained in:
Ryan Heise 2020-09-26 23:30:17 +10:00
parent ab6c8b2a8a
commit 3a44f844e2
2 changed files with 153 additions and 28 deletions

View File

@ -46,6 +46,7 @@ abstract class JustAudioPlatform extends PlatformInterface {
/// A nested platform interface for communicating with a particular player /// A nested platform interface for communicating with a particular player
/// instance. /// instance.
abstract class AudioPlayerPlatform { abstract class AudioPlayerPlatform {
Stream<PlaybackEventMessage> get playbackEventMessageStream;
Future<LoadResponse> load(LoadRequest request); Future<LoadResponse> load(LoadRequest request);
Future<PlayResponse> play(PlayRequest request); Future<PlayResponse> play(PlayRequest request);
Future<PauseResponse> pause(PauseRequest request); Future<PauseResponse> pause(PauseRequest request);
@ -68,6 +69,112 @@ abstract class AudioPlayerPlatform {
ConcatenatingMoveRequest request); ConcatenatingMoveRequest request);
} }
class PlaybackEventMessage {
final ProcessingStateMessage processingState;
final DateTime updateTime;
final Duration updatePosition;
final Duration bufferedPosition;
final Duration duration;
final IcyMetadataMessage icyMetadata;
final int currentIndex;
final int androidAudioSessionId;
PlaybackEventMessage({
@required this.processingState,
@required this.updateTime,
@required this.updatePosition,
@required this.bufferedPosition,
@required this.duration,
@required this.icyMetadata,
@required this.currentIndex,
@required this.androidAudioSessionId,
});
static PlaybackEventMessage fromMap(Map<dynamic, dynamic> map) =>
PlaybackEventMessage(
processingState: ProcessingStateMessage.values[map['processingState']],
updateTime: DateTime.fromMillisecondsSinceEpoch(map['updateTime']),
// TODO: Ensure all platforms pass a microsecond value.
updatePosition: Duration(microseconds: map['updatePosition']),
// TODO: Ensure all platforms pass a microsecond value.
bufferedPosition: Duration(microseconds: map['bufferedPosition']),
// TODO: Ensure all platforms pass a microsecond value.
duration: map['duration'] == null || map['duration'] < 0
? null
: Duration(microseconds: map['duration']),
icyMetadata: map['icyMetadata'] == null
? null
: IcyMetadataMessage.fromMap(map['icyMetadata']),
currentIndex: map['currentIndex'],
androidAudioSessionId: map['androidAudioSessionId'],
);
}
enum ProcessingStateMessage {
none,
loading,
buffering,
ready,
completed,
}
class IcyMetadataMessage {
final IcyInfoMessage info;
final IcyHeadersMessage headers;
IcyMetadataMessage({
@required this.info,
@required this.headers,
});
static IcyMetadataMessage fromMap(Map<dynamic, dynamic> json) =>
IcyMetadataMessage(
info:
json['info'] == null ? null : IcyInfoMessage.fromMap(json['info']),
headers: json['headers'] == null
? null
: IcyHeadersMessage.fromMap(json['headers']),
);
}
class IcyInfoMessage {
final String title;
final String url;
IcyInfoMessage({@required this.title, @required this.url});
static IcyInfoMessage fromMap(Map<dynamic, dynamic> json) =>
IcyInfoMessage(title: json['title'], url: json['url']);
}
class IcyHeadersMessage {
final int bitrate;
final String genre;
final String name;
final int metadataInterval;
final String url;
final bool isPublic;
IcyHeadersMessage({
@required this.bitrate,
@required this.genre,
@required this.name,
@required this.metadataInterval,
@required this.url,
@required this.isPublic,
});
static IcyHeadersMessage fromMap(Map<dynamic, dynamic> json) =>
IcyHeadersMessage(
bitrate: json['bitrate'],
genre: json['genre'],
name: json['name'],
metadataInterval: json['metadataInterval'],
url: json['url'],
isPublic: json['isPublic'],
);
}
class InitRequest { class InitRequest {
final String id; final String id;
@ -344,6 +451,7 @@ class ProgressiveAudioSourceMessage extends UriAudioSourceMessage {
@override @override
Map<dynamic, dynamic> toMap() => { Map<dynamic, dynamic> toMap() => {
'type': 'progressive',
'id': id, 'id': id,
'uri': uri, 'uri': uri,
'headers': headers, 'headers': headers,
@ -359,6 +467,7 @@ class DashAudioSourceMessage extends UriAudioSourceMessage {
@override @override
Map<dynamic, dynamic> toMap() => { Map<dynamic, dynamic> toMap() => {
'type': 'dash',
'id': id, 'id': id,
'uri': uri, 'uri': uri,
'headers': headers, 'headers': headers,
@ -374,6 +483,7 @@ class HlsAudioSourceMessage extends UriAudioSourceMessage {
@override @override
Map<dynamic, dynamic> toMap() => { Map<dynamic, dynamic> toMap() => {
'type': 'hls',
'id': id, 'id': id,
'uri': uri, 'uri': uri,
'headers': headers, 'headers': headers,
@ -392,7 +502,9 @@ class ConcatenatingAudioSourceMessage extends AudioSourceMessage {
@override @override
Map<dynamic, dynamic> toMap() => { Map<dynamic, dynamic> toMap() => {
'type': 'concatenating',
'id': id, 'id': id,
// TODO: ensure platform implementation uses this key
'children': children.map((child) => child.toMap()).toList(), 'children': children.map((child) => child.toMap()).toList(),
'useLazyPreparation': useLazyPreparation, 'useLazyPreparation': useLazyPreparation,
}; };
@ -412,9 +524,13 @@ class ClippingAudioSourceMessage extends IndexedAudioSourceMessage {
@override @override
Map<dynamic, dynamic> toMap() => { Map<dynamic, dynamic> toMap() => {
'type': 'clipping',
'id': id, 'id': id,
// TODO: ensure platform implementation uses this key
'child': child.toMap(), 'child': child.toMap(),
// TODO: ensure platform implementation interprets in Us.
'start': start.inMicroseconds, 'start': start.inMicroseconds,
// TODO: ensure platform implementation interprets in Us.
'end': end.inMicroseconds, 'end': end.inMicroseconds,
}; };
} }
@ -431,7 +547,9 @@ class LoopingAudioSourceMessage extends AudioSourceMessage {
@override @override
Map<dynamic, dynamic> toMap() => { Map<dynamic, dynamic> toMap() => {
'type': 'looping',
'id': id, 'id': id,
// TODO: ensure platform implementation uses this key
'child': child.toMap(), 'child': child.toMap(),
'count': count, 'count': count,
}; };

View File

@ -22,94 +22,101 @@ class MethodChannelAudioPlayer extends AudioPlayerPlatform {
MethodChannelAudioPlayer(this.id) MethodChannelAudioPlayer(this.id)
: _channel = MethodChannel('com.ryanheise.just_audio.methods.$id'); : _channel = MethodChannel('com.ryanheise.just_audio.methods.$id');
@override
Stream<PlaybackEventMessage> get playbackEventMessageStream =>
EventChannel('com.ryanheise.just_audio.events.$id')
.receiveBroadcastStream()
.map((map) => PlaybackEventMessage.fromMap(map));
@override @override
Future<LoadResponse> load(LoadRequest request) async { Future<LoadResponse> load(LoadRequest request) async {
return (await _channel.invokeMethod('load', request?.toMap()))?.fromMap(); return LoadResponse.fromMap(
await _channel.invokeMethod('load', request?.toMap()));
} }
@override @override
Future<PlayResponse> play(PlayRequest request) async { Future<PlayResponse> play(PlayRequest request) async {
return (await _channel.invokeMethod('play', request?.toMap()))?.fromMap(); return PlayResponse.fromMap(
await _channel.invokeMethod('play', request?.toMap()));
} }
@override @override
Future<PauseResponse> pause(PauseRequest request) async { Future<PauseResponse> pause(PauseRequest request) async {
return (await _channel.invokeMethod('pause', request?.toMap()))?.fromMap(); return PauseResponse.fromMap(
await _channel.invokeMethod('pause', request?.toMap()));
} }
@override @override
Future<SetVolumeResponse> setVolume(SetVolumeRequest request) async { Future<SetVolumeResponse> setVolume(SetVolumeRequest request) async {
return (await _channel.invokeMethod('setVolume', request?.toMap())) return SetVolumeResponse.fromMap(
?.fromMap(); await _channel.invokeMethod('setVolume', request?.toMap()));
} }
@override @override
Future<SetSpeedResponse> setSpeed(SetSpeedRequest request) async { Future<SetSpeedResponse> setSpeed(SetSpeedRequest request) async {
return (await _channel.invokeMethod('setSpeed', request?.toMap())) return SetSpeedResponse.fromMap(
?.fromMap(); await _channel.invokeMethod('setSpeed', request?.toMap()));
} }
@override @override
Future<SetLoopModeResponse> setLoopMode(SetLoopModeRequest request) async { Future<SetLoopModeResponse> setLoopMode(SetLoopModeRequest request) async {
return (await _channel.invokeMethod('setLoopMode', request?.toMap())) return SetLoopModeResponse.fromMap(
?.fromMap(); await _channel.invokeMethod('setLoopMode', request?.toMap()));
} }
@override @override
Future<SetShuffleModeResponse> setShuffleMode( Future<SetShuffleModeResponse> setShuffleMode(
SetShuffleModeRequest request) async { SetShuffleModeRequest request) async {
return (await _channel.invokeMethod('setShuffleMode', request?.toMap())) return SetShuffleModeResponse.fromMap(
?.fromMap(); await _channel.invokeMethod('setShuffleMode', request?.toMap()));
} }
@override @override
Future<SetAutomaticallyWaitsToMinimizeStallingResponse> Future<SetAutomaticallyWaitsToMinimizeStallingResponse>
setAutomaticallyWaitsToMinimizeStalling( setAutomaticallyWaitsToMinimizeStalling(
SetAutomaticallyWaitsToMinimizeStallingRequest request) async { SetAutomaticallyWaitsToMinimizeStallingRequest request) async {
return (await _channel.invokeMethod( return SetAutomaticallyWaitsToMinimizeStallingResponse.fromMap(
'setAutomaticallyWaitsToMinimizeStalling', request?.toMap())) await _channel.invokeMethod(
?.fromMap(); 'setAutomaticallyWaitsToMinimizeStalling', request?.toMap()));
} }
@override @override
Future<SeekResponse> seek(SeekRequest request) async { Future<SeekResponse> seek(SeekRequest request) async {
return (await _channel.invokeMethod('seek', request?.toMap()))?.fromMap(); return SeekResponse.fromMap(
await _channel.invokeMethod('seek', request?.toMap()));
} }
@override @override
Future<SetAndroidAudioAttributesResponse> setAndroidAudioAttributes( Future<SetAndroidAudioAttributesResponse> setAndroidAudioAttributes(
SetAndroidAudioAttributesRequest request) async { SetAndroidAudioAttributesRequest request) async {
return (await _channel.invokeMethod( return SetAndroidAudioAttributesResponse.fromMap(await _channel
'setAndroidAudioAttributes', request?.toMap())) .invokeMethod('setAndroidAudioAttributes', request?.toMap()));
?.fromMap();
} }
@override @override
Future<DisposeResponse> dispose(DisposeRequest request) async { Future<DisposeResponse> dispose(DisposeRequest request) async {
return (await _channel.invokeMethod('dispose', request?.toMap())) return DisposeResponse.fromMap(
?.fromMap(); await _channel.invokeMethod('dispose', request?.toMap()));
} }
@override @override
Future<ConcatenatingInsertAllResponse> concatenatingInsertAll( Future<ConcatenatingInsertAllResponse> concatenatingInsertAll(
ConcatenatingInsertAllRequest request) async { ConcatenatingInsertAllRequest request) async {
return (await _channel.invokeMethod( return ConcatenatingInsertAllResponse.fromMap(await _channel.invokeMethod(
'concatenatingInsertAll', request?.toMap())) 'concatenatingInsertAll', request?.toMap()));
?.fromMap();
} }
@override @override
Future<ConcatenatingRemoveRangeResponse> concatenatingRemoveRange( Future<ConcatenatingRemoveRangeResponse> concatenatingRemoveRange(
ConcatenatingRemoveRangeRequest request) async { ConcatenatingRemoveRangeRequest request) async {
return (await _channel.invokeMethod( return ConcatenatingRemoveRangeResponse.fromMap(await _channel.invokeMethod(
'concatenatingRemoveRange', request?.toMap())) 'concatenatingRemoveRange', request?.toMap()));
?.fromMap();
} }
@override @override
Future<ConcatenatingMoveResponse> concatenatingMove( Future<ConcatenatingMoveResponse> concatenatingMove(
ConcatenatingMoveRequest request) async { ConcatenatingMoveRequest request) async {
return (await _channel.invokeMethod('concatenatingMove', request?.toMap())) return ConcatenatingMoveResponse.fromMap(
?.fromMap(); await _channel.invokeMethod('concatenatingMove', request?.toMap()));
} }
} }