diff --git a/just_audio_platform_interface/.gitignore b/just_audio_platform_interface/.gitignore new file mode 100644 index 0000000..50602ac --- /dev/null +++ b/just_audio_platform_interface/.gitignore @@ -0,0 +1,11 @@ +# Files and directories created by pub +.dart_tool/ +.packages +# Remove the following pattern if you wish to check in your lock file +pubspec.lock + +# Conventional directory for build outputs +build/ + +# Directory created by dartdoc +doc/api/ diff --git a/just_audio_platform_interface/CHANGELOG.md b/just_audio_platform_interface/CHANGELOG.md new file mode 100644 index 0000000..b78d64c --- /dev/null +++ b/just_audio_platform_interface/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +- Initial version. diff --git a/just_audio_platform_interface/LICENSE b/just_audio_platform_interface/LICENSE new file mode 100644 index 0000000..5fcf62e --- /dev/null +++ b/just_audio_platform_interface/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Ryan Heise and the project contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/just_audio_platform_interface/README.md b/just_audio_platform_interface/README.md new file mode 100644 index 0000000..fcd8eeb --- /dev/null +++ b/just_audio_platform_interface/README.md @@ -0,0 +1,26 @@ +# just_audio_platform_interface + +A common platform interface for the [`just_audio`][../just_audio] plugin. + +This interface allows platform-specific implementations of the `just_audio` +plugin, as well as the plugin itself, to ensure they are supporting the +same interface. + +# Usage + +To implement a new platform-specific implementation of `just_audio`, extend +[`JustAudioPlatform`][2] with an implementation that performs the +platform-specific behavior, and when you register your plugin, set the default +`JustAudioPlatform` by calling +`JustAudioPlatform.instance = MyPlatformJustAudio()`. + +# Note on breaking changes + +Strongly prefer non-breaking changes (such as adding a method to the interface) +over breaking changes for this package. + +See https://flutter.dev/go/platform-interface-breaking-changes for a discussion +on why a less-clean interface is preferable to a breaking change. + +[1]: ../just_audio +[2]: lib/just_audio_platform_interface.dart diff --git a/just_audio_platform_interface/lib/just_audio_platform_interface.dart b/just_audio_platform_interface/lib/just_audio_platform_interface.dart new file mode 100644 index 0000000..40523ab --- /dev/null +++ b/just_audio_platform_interface/lib/just_audio_platform_interface.dart @@ -0,0 +1,426 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:meta/meta.dart' show required; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'method_channel_just_audio.dart'; + +/// The interface that implementations of just_audio must implement. +/// +/// Platform implementations should extend this class rather than implement it +/// as `just_audio` does not consider newly added methods to be breaking +/// changes. Extending this class (using `extends`) ensures that the subclass +/// will get the default implementation, while platform implementations that +/// `implements` this interface will be broken by newly added +/// [JustAudioPlatform] methods. +abstract class JustAudioPlatform extends PlatformInterface { + /// Constructs a JustAudioPlatform. + JustAudioPlatform() : super(token: _token); + + static final Object _token = Object(); + + static JustAudioPlatform _instance = MethodChannelJustAudio(); + + /// The default instance of [JustAudioPlatform] to use. + /// + /// Defaults to [MethodChannelJustAudio]. + static JustAudioPlatform get instance => _instance; + + /// Platform-specific plugins should set this with their own platform-specific + /// class that extends [JustAudioPlatform] when they register themselves. + // TODO(amirh): Extract common platform interface logic. + // https://github.com/flutter/flutter/issues/43368 + static set instance(JustAudioPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + /// Creates a new player and returns a nested platform interface for + /// communicating with that player. + Future init(InitRequest request) { + throw UnimplementedError('init() has not been implemented.'); + } +} + +/// A nested platform interface for communicating with a particular player +/// instance. +abstract class AudioPlayerPlatform { + Future load(LoadRequest request); + Future play(PlayRequest request); + Future pause(PauseRequest request); + Future setVolume(SetVolumeRequest request); + Future setSpeed(SetSpeedRequest request); + Future setLoopMode(SetLoopModeRequest request); + Future setShuffleMode(SetShuffleModeRequest request); + Future + setAutomaticallyWaitsToMinimizeStalling( + SetAutomaticallyWaitsToMinimizeStallingRequest request); + Future seek(SeekRequest request); + Future setAndroidAudioAttributes( + SetAndroidAudioAttributesRequest request); + Future dispose(DisposeRequest request); + Future concatenatingInsertAll( + ConcatenatingInsertAllRequest request); + Future concatenatingRemoveRange( + ConcatenatingRemoveRangeRequest request); + Future concatenatingMove( + ConcatenatingMoveRequest request); +} + +class InitRequest { + final String id; + + InitRequest({@required this.id}); + + Map toMap() => { + 'id': id, + }; +} + +class LoadRequest { + final AudioSourceMessage audioSourceMessage; + + LoadRequest({@required this.audioSourceMessage}); + + Map toMap() => { + 'audioSource': audioSourceMessage.toMap(), + }; +} + +class LoadResponse { + final Duration duration; + + LoadResponse({@required this.duration}); + + static LoadResponse fromMap(Map map) => LoadResponse( + duration: map['duration'] != null + ? Duration(microseconds: map['duration']) + : null); +} + +class PlayRequest { + Map toMap() => {}; +} + +class PlayResponse { + static PlayResponse fromMap(Map map) => PlayResponse(); +} + +class PauseRequest { + Map toMap() => {}; +} + +class PauseResponse { + static PauseResponse fromMap(Map map) => PauseResponse(); +} + +class SetVolumeRequest { + final double volume; + + SetVolumeRequest({@required this.volume}); + + Map toMap() => { + 'volume': volume, + }; +} + +class SetVolumeResponse { + static SetVolumeResponse fromMap(Map map) => + SetVolumeResponse(); +} + +class SetSpeedRequest { + final double speed; + + SetSpeedRequest({@required this.speed}); + + Map toMap() => { + 'speed': speed, + }; +} + +class SetSpeedResponse { + static SetSpeedResponse fromMap(Map map) => + SetSpeedResponse(); +} + +class SetLoopModeRequest { + final LoopModeMessage loopMode; + + SetLoopModeRequest({@required this.loopMode}); + + Map toMap() => { + 'loopMode': describeEnum(loopMode), + }; +} + +class SetLoopModeResponse { + static SetLoopModeResponse fromMap(Map map) => + SetLoopModeResponse(); +} + +enum LoopModeMessage { off, one, all } + +class SetShuffleModeRequest { + final ShuffleModeMessage shuffleMode; + + SetShuffleModeRequest({@required this.shuffleMode}); + + Map toMap() => { + 'shuffleMode': describeEnum(shuffleMode), + }; +} + +class SetShuffleModeResponse { + static SetShuffleModeResponse fromMap(Map map) => + SetShuffleModeResponse(); +} + +enum ShuffleModeMessage { none, all } + +class SetAutomaticallyWaitsToMinimizeStallingRequest { + final bool enabled; + + SetAutomaticallyWaitsToMinimizeStallingRequest({@required this.enabled}); + + Map toMap() => { + 'enabled': enabled, + }; +} + +class SetAutomaticallyWaitsToMinimizeStallingResponse { + static SetAutomaticallyWaitsToMinimizeStallingResponse fromMap( + Map map) => + SetAutomaticallyWaitsToMinimizeStallingResponse(); +} + +class SeekRequest { + final Duration position; + final int index; + + SeekRequest({@required this.position, this.index}); + + Map toMap() => { + 'position': position.inMicroseconds, + 'index': index, + }; +} + +class SeekResponse { + static SeekResponse fromMap(Map map) => SeekResponse(); +} + +class SetAndroidAudioAttributesRequest { + final int contentType; + final int flags; + final int usage; + + SetAndroidAudioAttributesRequest({ + @required this.contentType, + @required this.flags, + @required this.usage, + }); + + Map toMap() => { + 'contentType': contentType, + 'flags': flags, + 'usage': usage, + }; +} + +class SetAndroidAudioAttributesResponse { + static SetAndroidAudioAttributesResponse fromMap(Map map) => + SetAndroidAudioAttributesResponse(); +} + +class DisposeRequest { + Map toMap() => {}; +} + +class DisposeResponse { + static DisposeResponse fromMap(Map map) => + DisposeResponse(); +} + +class ConcatenatingInsertAllRequest { + final int index; + final List children; + + ConcatenatingInsertAllRequest({ + @required this.index, + @required this.children, + }); + + Map toMap() => { + 'index': index, + 'children': children.map((child) => child.toMap()).toList(), + }; +} + +class ConcatenatingInsertAllResponse { + static ConcatenatingInsertAllResponse fromMap(Map map) => + ConcatenatingInsertAllResponse(); +} + +class ConcatenatingRemoveRangeRequest { + final int startIndex; + final int endIndex; + + ConcatenatingRemoveRangeRequest({ + @required this.startIndex, + @required this.endIndex, + }); + + Map toMap() => { + 'startIndex': startIndex, + 'endIndex': endIndex, + }; +} + +class ConcatenatingRemoveRangeResponse { + static ConcatenatingRemoveRangeResponse fromMap(Map map) => + ConcatenatingRemoveRangeResponse(); +} + +class ConcatenatingMoveRequest { + final int currentIndex; + final int newIndex; + + ConcatenatingMoveRequest({this.currentIndex, this.newIndex}); + + Map toMap() => { + 'currentIndex': currentIndex, + 'newIndex': newIndex, + }; +} + +class ConcatenatingMoveResponse { + static ConcatenatingMoveResponse fromMap(Map map) => + ConcatenatingMoveResponse(); +} + +abstract class AudioSourceMessage { + final String id; + + AudioSourceMessage({@required this.id}); + + Map toMap(); +} + +abstract class IndexedAudioSourceMessage extends AudioSourceMessage { + IndexedAudioSourceMessage({@required String id}) : super(id: id); +} + +abstract class UriAudioSourceMessage extends IndexedAudioSourceMessage { + final String uri; + final Map headers; + + UriAudioSourceMessage({ + @required String id, + @required this.uri, + @required this.headers, + }) : super(id: id); +} + +class ProgressiveAudioSourceMessage extends UriAudioSourceMessage { + ProgressiveAudioSourceMessage({ + @required String id, + @required String uri, + @required Map headers, + }) : super(id: id, uri: uri, headers: headers); + + @override + Map toMap() => { + 'id': id, + 'uri': uri, + 'headers': headers, + }; +} + +class DashAudioSourceMessage extends UriAudioSourceMessage { + DashAudioSourceMessage({ + @required String id, + @required String uri, + @required Map headers, + }) : super(id: id, uri: uri, headers: headers); + + @override + Map toMap() => { + 'id': id, + 'uri': uri, + 'headers': headers, + }; +} + +class HlsAudioSourceMessage extends UriAudioSourceMessage { + HlsAudioSourceMessage({ + @required String id, + @required String uri, + @required Map headers, + }) : super(id: id, uri: uri, headers: headers); + + @override + Map toMap() => { + 'id': id, + 'uri': uri, + 'headers': headers, + }; +} + +class ConcatenatingAudioSourceMessage extends AudioSourceMessage { + final List children; + final bool useLazyPreparation; + + ConcatenatingAudioSourceMessage({ + @required String id, + @required this.children, + @required this.useLazyPreparation, + }) : super(id: id); + + @override + Map toMap() => { + 'id': id, + 'children': children.map((child) => child.toMap()).toList(), + 'useLazyPreparation': useLazyPreparation, + }; +} + +class ClippingAudioSourceMessage extends IndexedAudioSourceMessage { + final UriAudioSourceMessage child; + final Duration start; + final Duration end; + + ClippingAudioSourceMessage({ + @required String id, + @required this.child, + @required this.start, + @required this.end, + }) : super(id: id); + + @override + Map toMap() => { + 'id': id, + 'child': child.toMap(), + 'start': start.inMicroseconds, + 'end': end.inMicroseconds, + }; +} + +class LoopingAudioSourceMessage extends AudioSourceMessage { + final AudioSourceMessage child; + final int count; + + LoopingAudioSourceMessage({ + @required String id, + @required this.child, + @required this.count, + }) : super(id: id); + + @override + Map toMap() => { + 'id': id, + 'child': child.toMap(), + 'count': count, + }; +} diff --git a/just_audio_platform_interface/lib/method_channel_just_audio.dart b/just_audio_platform_interface/lib/method_channel_just_audio.dart new file mode 100644 index 0000000..0cac213 --- /dev/null +++ b/just_audio_platform_interface/lib/method_channel_just_audio.dart @@ -0,0 +1,115 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; + +import 'just_audio_platform_interface.dart'; + +/// An implementation of [JustAudioPlatform] that uses method channels. +class MethodChannelJustAudio extends JustAudioPlatform { + static final _mainChannel = MethodChannel('com.ryanheise.just_audio.methods'); + + @override + Future init(InitRequest request) async { + await _mainChannel.invokeMethod('init', request.toMap()); + return MethodChannelAudioPlayer(request.id); + } +} + +class MethodChannelAudioPlayer extends AudioPlayerPlatform { + final String id; + final MethodChannel _channel; + + MethodChannelAudioPlayer(this.id) + : _channel = MethodChannel('com.ryanheise.just_audio.methods.$id'); + + @override + Future load(LoadRequest request) async { + return (await _channel.invokeMethod('load', request?.toMap()))?.fromMap(); + } + + @override + Future play(PlayRequest request) async { + return (await _channel.invokeMethod('play', request?.toMap()))?.fromMap(); + } + + @override + Future pause(PauseRequest request) async { + return (await _channel.invokeMethod('pause', request?.toMap()))?.fromMap(); + } + + @override + Future setVolume(SetVolumeRequest request) async { + return (await _channel.invokeMethod('setVolume', request?.toMap())) + ?.fromMap(); + } + + @override + Future setSpeed(SetSpeedRequest request) async { + return (await _channel.invokeMethod('setSpeed', request?.toMap())) + ?.fromMap(); + } + + @override + Future setLoopMode(SetLoopModeRequest request) async { + return (await _channel.invokeMethod('setLoopMode', request?.toMap())) + ?.fromMap(); + } + + @override + Future setShuffleMode( + SetShuffleModeRequest request) async { + return (await _channel.invokeMethod('setShuffleMode', request?.toMap())) + ?.fromMap(); + } + + @override + Future + setAutomaticallyWaitsToMinimizeStalling( + SetAutomaticallyWaitsToMinimizeStallingRequest request) async { + return (await _channel.invokeMethod( + 'setAutomaticallyWaitsToMinimizeStalling', request?.toMap())) + ?.fromMap(); + } + + @override + Future seek(SeekRequest request) async { + return (await _channel.invokeMethod('seek', request?.toMap()))?.fromMap(); + } + + @override + Future setAndroidAudioAttributes( + SetAndroidAudioAttributesRequest request) async { + return (await _channel.invokeMethod( + 'setAndroidAudioAttributes', request?.toMap())) + ?.fromMap(); + } + + @override + Future dispose(DisposeRequest request) async { + return (await _channel.invokeMethod('dispose', request?.toMap())) + ?.fromMap(); + } + + @override + Future concatenatingInsertAll( + ConcatenatingInsertAllRequest request) async { + return (await _channel.invokeMethod( + 'concatenatingInsertAll', request?.toMap())) + ?.fromMap(); + } + + @override + Future concatenatingRemoveRange( + ConcatenatingRemoveRangeRequest request) async { + return (await _channel.invokeMethod( + 'concatenatingRemoveRange', request?.toMap())) + ?.fromMap(); + } + + @override + Future concatenatingMove( + ConcatenatingMoveRequest request) async { + return (await _channel.invokeMethod('concatenatingMove', request?.toMap())) + ?.fromMap(); + } +} diff --git a/just_audio_platform_interface/pubspec.yaml b/just_audio_platform_interface/pubspec.yaml new file mode 100644 index 0000000..e3f45a2 --- /dev/null +++ b/just_audio_platform_interface/pubspec.yaml @@ -0,0 +1,22 @@ +name: just_audio_platform_interface +description: A common platform interface for the just_audio plugin. +homepage: https://github.com/ryanheise/just_audio/just_audio_platform_interface +# NOTE: We strongly prefer non-breaking changes, even at the expense of a +# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes +version: 0.0.1 + +dependencies: + flutter: + sdk: flutter + meta: ^1.1.8 + plugin_platform_interface: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + mockito: ^4.1.1 + pedantic: ^1.8.0 + +environment: + sdk: ">=2.7.0 <3.0.0" + flutter: ">=1.12.13+hotfix.5"