diff --git a/audio_service/lib/audio_service.dart b/audio_service/lib/audio_service.dart index 94ef1126..4888207b 100644 --- a/audio_service/lib/audio_service.dart +++ b/audio_service/lib/audio_service.dart @@ -662,193 +662,195 @@ class AudioService { _platform.setHandlerCallbacks(_HandlerCallbacks(handler)); // This port listens to connections from other isolates. - _customActionReceivePort = ReceivePort(); - _customActionReceivePort.listen((dynamic event) async { - final request = event as _IsolateRequest; - switch (request.method) { - case 'prepare': - await _handler.prepare(); - request.sendPort.send(null); - break; - case 'prepareFromMediaId': - await _handler.prepareFromMediaId( - request.arguments![0], request.arguments![1]); - request.sendPort.send(null); - break; - case 'prepareFromSearch': - await _handler.prepareFromSearch( - request.arguments![0], request.arguments![1]); - request.sendPort.send(null); - break; - case 'prepareFromUri': - await _handler.prepareFromUri( - request.arguments![0], request.arguments![1]); - request.sendPort.send(null); - break; - case 'play': - await _handler.play(); - request.sendPort.send(null); - break; - case 'playFromMediaId': - await _handler.playFromMediaId( - request.arguments![0], request.arguments![1]); - request.sendPort.send(null); - break; - case 'playFromSearch': - await _handler.playFromSearch( - request.arguments![0], request.arguments![1]); - request.sendPort.send(null); - break; - case 'playFromUri': - await _handler.playFromUri( - request.arguments![0], request.arguments![1]); - request.sendPort.send(null); - break; - case 'playMediaItem': - await _handler.playMediaItem(request.arguments![0]); - request.sendPort.send(null); - break; - case 'pause': - await _handler.pause(); - request.sendPort.send(null); - break; - case 'click': - await _handler.click(request.arguments![0]); - request.sendPort.send(null); - break; - case 'stop': - await _handler.stop(); - request.sendPort.send(null); - break; - case 'addQueueItem': - await _handler.addQueueItem(request.arguments![0]); - request.sendPort.send(null); - break; - case 'addQueueItems': - await _handler.addQueueItems(request.arguments![0]); - request.sendPort.send(null); - break; - case 'insertQueueItem': - await _handler.insertQueueItem( - request.arguments![0], request.arguments![1]); - request.sendPort.send(null); - break; - case 'updateQueue': - await _handler.updateQueue(request.arguments![0]); - request.sendPort.send(null); - break; - case 'updateMediaItem': - await _handler.updateMediaItem(request.arguments![0]); - request.sendPort.send(null); - break; - case 'removeQueueItem': - await _handler.removeQueueItem(request.arguments![0]); - request.sendPort.send(null); - break; - case 'removeQueueItemAt': - await _handler.removeQueueItemAt(request.arguments![0]); - request.sendPort.send(null); - break; - case 'skipToNext': - await _handler.skipToNext(); - request.sendPort.send(null); - break; - case 'skipToPrevious': - await _handler.skipToPrevious(); - request.sendPort.send(null); - break; - case 'fastForward': - await _handler.fastForward(); - request.sendPort.send(null); - break; - case 'rewind': - await _handler.rewind(); - request.sendPort.send(null); - break; - case 'skipToQueueItem': - await _handler.skipToQueueItem(request.arguments![0]); - request.sendPort.send(null); - break; - case 'seek': - await _handler.seek(request.arguments![0]); - request.sendPort.send(null); - break; - case 'setRating': - await _handler.setRating( - request.arguments![0], request.arguments![1]); - request.sendPort.send(null); - break; - case 'setCaptioningEnabled': - await _handler.setCaptioningEnabled(request.arguments![0]); - request.sendPort.send(null); - break; - case 'setRepeatMode': - await _handler.setRepeatMode(request.arguments![0]); - request.sendPort.send(null); - break; - case 'setShuffleMode': - await _handler.setShuffleMode(request.arguments![0]); - request.sendPort.send(null); - break; - case 'seekBackward': - await _handler.seekBackward(request.arguments![0]); - request.sendPort.send(null); - break; - case 'seekForward': - await _handler.seekForward(request.arguments![0]); - request.sendPort.send(null); - break; - case 'setSpeed': - await _handler.setSpeed(request.arguments![0]); - request.sendPort.send(null); - break; - case 'customAction': - await _handler.customAction( - request.arguments![0], request.arguments![1]); - request.sendPort.send(null); - break; - case 'onTaskRemoved': - await _handler.onTaskRemoved(); - request.sendPort.send(null); - break; - case 'onNotificationDeleted': - await _handler.onNotificationDeleted(); - request.sendPort.send(null); - break; - case 'getChildren': - request.sendPort.send(await _handler.getChildren( - request.arguments![0], request.arguments![1])); - break; - case 'subscribeToChildren': - final parentMediaId = request.arguments![0] as String; - final sendPort = request.arguments![1] as SendPort?; - _handler - .subscribeToChildren(parentMediaId) - .listen((Map? options) { - sendPort!.send(options); - }); - break; - case 'getMediaItem': - request.sendPort - .send(await _handler.getMediaItem(request.arguments![0])); - break; - case 'search': - request.sendPort.send(await _handler.search( - request.arguments![0], request.arguments![1])); - break; - case 'androidAdjustRemoteVolume': - await _handler.androidAdjustRemoteVolume(request.arguments![0]); - request.sendPort.send(null); - break; - case 'androidSetRemoteVolume': - await _handler.androidSetRemoteVolume(request.arguments![0]); - request.sendPort.send(null); - break; - } - }); - //IsolateNameServer.removePortNameMapping(_isolatePortName); - IsolateNameServer.registerPortWithName( - _customActionReceivePort.sendPort, _isolatePortName); + if (!kIsWeb) { + _customActionReceivePort = ReceivePort(); + _customActionReceivePort.listen((dynamic event) async { + final request = event as _IsolateRequest; + switch (request.method) { + case 'prepare': + await _handler.prepare(); + request.sendPort.send(null); + break; + case 'prepareFromMediaId': + await _handler.prepareFromMediaId( + request.arguments![0], request.arguments![1]); + request.sendPort.send(null); + break; + case 'prepareFromSearch': + await _handler.prepareFromSearch( + request.arguments![0], request.arguments![1]); + request.sendPort.send(null); + break; + case 'prepareFromUri': + await _handler.prepareFromUri( + request.arguments![0], request.arguments![1]); + request.sendPort.send(null); + break; + case 'play': + await _handler.play(); + request.sendPort.send(null); + break; + case 'playFromMediaId': + await _handler.playFromMediaId( + request.arguments![0], request.arguments![1]); + request.sendPort.send(null); + break; + case 'playFromSearch': + await _handler.playFromSearch( + request.arguments![0], request.arguments![1]); + request.sendPort.send(null); + break; + case 'playFromUri': + await _handler.playFromUri( + request.arguments![0], request.arguments![1]); + request.sendPort.send(null); + break; + case 'playMediaItem': + await _handler.playMediaItem(request.arguments![0]); + request.sendPort.send(null); + break; + case 'pause': + await _handler.pause(); + request.sendPort.send(null); + break; + case 'click': + await _handler.click(request.arguments![0]); + request.sendPort.send(null); + break; + case 'stop': + await _handler.stop(); + request.sendPort.send(null); + break; + case 'addQueueItem': + await _handler.addQueueItem(request.arguments![0]); + request.sendPort.send(null); + break; + case 'addQueueItems': + await _handler.addQueueItems(request.arguments![0]); + request.sendPort.send(null); + break; + case 'insertQueueItem': + await _handler.insertQueueItem( + request.arguments![0], request.arguments![1]); + request.sendPort.send(null); + break; + case 'updateQueue': + await _handler.updateQueue(request.arguments![0]); + request.sendPort.send(null); + break; + case 'updateMediaItem': + await _handler.updateMediaItem(request.arguments![0]); + request.sendPort.send(null); + break; + case 'removeQueueItem': + await _handler.removeQueueItem(request.arguments![0]); + request.sendPort.send(null); + break; + case 'removeQueueItemAt': + await _handler.removeQueueItemAt(request.arguments![0]); + request.sendPort.send(null); + break; + case 'skipToNext': + await _handler.skipToNext(); + request.sendPort.send(null); + break; + case 'skipToPrevious': + await _handler.skipToPrevious(); + request.sendPort.send(null); + break; + case 'fastForward': + await _handler.fastForward(); + request.sendPort.send(null); + break; + case 'rewind': + await _handler.rewind(); + request.sendPort.send(null); + break; + case 'skipToQueueItem': + await _handler.skipToQueueItem(request.arguments![0]); + request.sendPort.send(null); + break; + case 'seek': + await _handler.seek(request.arguments![0]); + request.sendPort.send(null); + break; + case 'setRating': + await _handler.setRating( + request.arguments![0], request.arguments![1]); + request.sendPort.send(null); + break; + case 'setCaptioningEnabled': + await _handler.setCaptioningEnabled(request.arguments![0]); + request.sendPort.send(null); + break; + case 'setRepeatMode': + await _handler.setRepeatMode(request.arguments![0]); + request.sendPort.send(null); + break; + case 'setShuffleMode': + await _handler.setShuffleMode(request.arguments![0]); + request.sendPort.send(null); + break; + case 'seekBackward': + await _handler.seekBackward(request.arguments![0]); + request.sendPort.send(null); + break; + case 'seekForward': + await _handler.seekForward(request.arguments![0]); + request.sendPort.send(null); + break; + case 'setSpeed': + await _handler.setSpeed(request.arguments![0]); + request.sendPort.send(null); + break; + case 'customAction': + await _handler.customAction( + request.arguments![0], request.arguments![1]); + request.sendPort.send(null); + break; + case 'onTaskRemoved': + await _handler.onTaskRemoved(); + request.sendPort.send(null); + break; + case 'onNotificationDeleted': + await _handler.onNotificationDeleted(); + request.sendPort.send(null); + break; + case 'getChildren': + request.sendPort.send(await _handler.getChildren( + request.arguments![0], request.arguments![1])); + break; + case 'subscribeToChildren': + final parentMediaId = request.arguments![0] as String; + final sendPort = request.arguments![1] as SendPort?; + _handler + .subscribeToChildren(parentMediaId) + .listen((Map? options) { + sendPort!.send(options); + }); + break; + case 'getMediaItem': + request.sendPort + .send(await _handler.getMediaItem(request.arguments![0])); + break; + case 'search': + request.sendPort.send(await _handler.search( + request.arguments![0], request.arguments![1])); + break; + case 'androidAdjustRemoteVolume': + await _handler.androidAdjustRemoteVolume(request.arguments![0]); + request.sendPort.send(null); + break; + case 'androidSetRemoteVolume': + await _handler.androidSetRemoteVolume(request.arguments![0]); + request.sendPort.send(null); + break; + } + }); + //IsolateNameServer.removePortNameMapping(_isolatePortName); + IsolateNameServer.registerPortWithName( + _customActionReceivePort.sendPort, _isolatePortName); + } _handler.mediaItem.listen((MediaItem? mediaItem) async { if (mediaItem == null) return; final artUri = mediaItem.artUri; diff --git a/audio_service/lib/audio_service_web.dart b/audio_service/lib/audio_service_web.dart deleted file mode 100644 index d3c92ce4..00000000 --- a/audio_service/lib/audio_service_web.dart +++ /dev/null @@ -1,346 +0,0 @@ -import 'dart:async'; -import 'dart:html' as html; -import 'dart:js' as js; - -import 'js/media_session_web.dart'; - -import 'package:audio_service/audio_service.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_web_plugins/flutter_web_plugins.dart'; - -const String _CUSTOM_PREFIX = 'custom_'; - -class AudioServicePlugin { - late int fastForwardInterval; - late int rewindInterval; - Map? params; - bool started = false; - late ClientHandler clientHandler; - late BackgroundHandler backgroundHandler; - - static void registerWith(Registrar registrar) { - AudioServicePlugin(registrar); - } - - AudioServicePlugin(Registrar registrar) { - clientHandler = ClientHandler(this, registrar); - backgroundHandler = BackgroundHandler(this, registrar); - } -} - -class ClientHandler { - final AudioServicePlugin plugin; - final MethodChannel channel; - - ClientHandler(this.plugin, Registrar registrar) - : channel = MethodChannel( - 'ryanheise.com/audioService', - const StandardMethodCodec(), - registrar, - ) { - channel.setMethodCallHandler(handleServiceMethodCall); - } - - Future invokeMethod(String method, [dynamic arguments]) => - channel.invokeMethod(method, arguments); - - Future handleServiceMethodCall(MethodCall call) async { - switch (call.method) { - case 'start': - plugin.fastForwardInterval = call.arguments['fastForwardInterval']; - plugin.rewindInterval = call.arguments['rewindInterval']; - plugin.params = call.arguments['params']; - plugin.started = true; - return plugin.started; - case 'connect': - // No-op not really anything for us to do with connect on the web, the - // streams should all be hydrated - break; - case 'disconnect': - // No-op not really anything for us to do with disconnect on the web, - // the streams should stay hydrated because everything is static and we - // aren't working with isolates - break; - case 'isRunning': - return plugin.started; - case 'rewind': - return plugin.backgroundHandler.invokeMethod('onRewind'); - case 'fastForward': - return plugin.backgroundHandler.invokeMethod('onFastForward'); - case 'skipToPrevious': - return plugin.backgroundHandler.invokeMethod('onSkipToPrevious'); - case 'skipToNext': - return plugin.backgroundHandler.invokeMethod('onSkipToNext'); - case 'play': - return plugin.backgroundHandler.invokeMethod('onPlay'); - case 'pause': - return plugin.backgroundHandler.invokeMethod('onPause'); - case 'stop': - return plugin.backgroundHandler.invokeMethod('onStop'); - case 'seekTo': - return plugin.backgroundHandler - .invokeMethod('onSeekTo', [call.arguments]); - case 'prepareFromMediaId': - return plugin.backgroundHandler - .invokeMethod('onPrepareFromMediaId', [call.arguments]); - case 'playFromMediaId': - return plugin.backgroundHandler - .invokeMethod('onPlayFromMediaId', [call.arguments]); - case 'setBrowseMediaParent': - return plugin.backgroundHandler - .invokeMethod('onLoadChildren', [call.arguments]); - case 'onClick': - // No-op we don't really have the idea of a bluetooth button click on - // the web - break; - case 'addQueueItem': - return plugin.backgroundHandler - .invokeMethod('onAddQueueItem', [call.arguments]); - case 'addQueueItemAt': - return plugin.backgroundHandler - .invokeMethod('onQueueItemAt', call.arguments); - case 'removeQueueItem': - return plugin.backgroundHandler - .invokeMethod('onRemoveQueueItem', [call.arguments]); - case 'updateQueue': - return plugin.backgroundHandler - .invokeMethod('onUpdateQueue', [call.arguments]); - case 'updateMediaItem': - return plugin.backgroundHandler - .invokeMethod('onUpdateMediaItem', [call.arguments]); - case 'prepare': - return plugin.backgroundHandler.invokeMethod('onPrepare'); - case 'playMediaItem': - return plugin.backgroundHandler - .invokeMethod('onPlayMediaItem', [call.arguments]); - case 'skipToQueueItem': - return plugin.backgroundHandler - .invokeMethod('onSkipToMediaItem', [call.arguments]); - case 'setRepeatMode': - return plugin.backgroundHandler - .invokeMethod('onSetRepeatMode', [call.arguments]); - case 'setShuffleMode': - return plugin.backgroundHandler - .invokeMethod('onSetShuffleMode', [call.arguments]); - case 'setRating': - return plugin.backgroundHandler.invokeMethod('onSetRating', - [call.arguments['rating'], call.arguments['extras']]); - case 'setSpeed': - return plugin.backgroundHandler - .invokeMethod('onSetSpeed', [call.arguments]); - default: - if (call.method.startsWith(_CUSTOM_PREFIX)) { - final result = await plugin.backgroundHandler - .invokeMethod(call.method, call.arguments); - return result; - } - throw PlatformException( - code: 'Unimplemented', - details: "The audio Service plugin for web doesn't implement " - "the method '${call.method}'"); - } - } -} - -class BackgroundHandler { - final AudioServicePlugin plugin; - final MethodChannel channel; - MediaItem? mediaItem; - - BackgroundHandler(this.plugin, Registrar registrar) - : channel = MethodChannel( - 'ryanheise.com/audioServiceBackground', - const StandardMethodCodec(), - registrar, - ) { - channel.setMethodCallHandler(handleBackgroundMethodCall); - } - - Future invokeMethod(String method, [dynamic arguments]) => - channel.invokeMethod(method, arguments); - - Future handleBackgroundMethodCall(MethodCall call) async { - switch (call.method) { - case 'started': - return started(call); - case 'ready': - return ready(call); - case 'stopped': - return stopped(call); - case 'setState': - return setState(call); - case 'setMediaItem': - return setMediaItem(call); - case 'setQueue': - return setQueue(call); - case 'androidForceEnableMediaButtons': - //no-op - break; - default: - throw PlatformException( - code: 'Unimplemented', - details: - "The audio service background plugin for web doesn't implement " - "the method '${call.method}'"); - } - } - - Future started(MethodCall call) async => true; - - Future ready(MethodCall call) async => { - 'fastForwardInterval': plugin.fastForwardInterval, - 'rewindInterval': plugin.rewindInterval, - 'params': plugin.params - }; - - Future stopped(MethodCall call) async { - final session = html.window.navigator.mediaSession!; - session.metadata = null; - plugin.started = false; - mediaItem = null; - plugin.clientHandler.invokeMethod('onStopped'); - } - - Future setState(MethodCall call) async { - final session = html.window.navigator.mediaSession!; - final List args = call.arguments!; - final List controls = call.arguments[0] - .map((element) => MediaControl( - action: MediaAction.values[element['action']], - androidIcon: element['androidIcon'], - label: element['label'])) - .toList(); - - // Reset the handlers - // TODO: Make this better... Like only change ones that have been changed - try { - session.setActionHandler('play', null); - session.setActionHandler('pause', null); - session.setActionHandler('previoustrack', null); - session.setActionHandler('nexttrack', null); - session.setActionHandler('seekbackward', null); - session.setActionHandler('seekforward', null); - session.setActionHandler('stop', null); - } catch (e) {} - - int actionBits = 0; - for (final control in controls) { - try { - switch (control.action) { - case MediaAction.play: - session.setActionHandler('play', AudioService.play); - break; - case MediaAction.pause: - session.setActionHandler('pause', AudioService.pause); - break; - case MediaAction.skipToPrevious: - session.setActionHandler( - 'previoustrack', AudioService.skipToPrevious); - break; - case MediaAction.skipToNext: - session.setActionHandler('nexttrack', AudioService.skipToNext); - break; - // The naming convention here is a bit odd but seekbackward seems more - // analagous to rewind than seekBackward - case MediaAction.rewind: - session.setActionHandler('seekbackward', AudioService.rewind); - break; - case MediaAction.fastForward: - session.setActionHandler('seekforward', AudioService.fastForward); - break; - case MediaAction.stop: - session.setActionHandler('stop', AudioService.stop); - break; - default: - // no-op - break; - } - } catch (e) {} - int actionCode = 1 << control.action.index; - actionBits |= actionCode; - } - - for (int rawSystemAction in call.arguments[1]) { - MediaAction action = MediaAction.values[rawSystemAction]; - - switch (action) { - case MediaAction.seek: - try { - setActionHandler('seekto', js.allowInterop((ActionResult ev) { - //print(ev.action); - //print(ev.seekTime); - // Chrome uses seconds for whatever reason - AudioService.seekTo(Duration( - milliseconds: (ev.seekTime * 1000).round(), - )); - })); - } catch (e) {} - break; - default: - // no-op - break; - } - - int actionCode = 1 << rawSystemAction; - actionBits |= actionCode; - } - - try { - // Dart also doesn't expose setPositionState - if (mediaItem != null) { - //print( - // 'Setting positionState Duration(${mediaItem!.duration?.inSeconds}), PlaybackRate(${args[6] ?? 1.0}), Position(${Duration(milliseconds: args[4])?.inSeconds})'); - - // Chrome looks for seconds for some reason - setPositionState(PositionState( - duration: (mediaItem!.duration?.inMilliseconds ?? 0) / 1000, - playbackRate: args[6] ?? 1.0, - position: (args[4] ?? 0) / 1000, - )); - } - } catch (e) { - print(e); - } - - plugin.clientHandler.invokeMethod('onPlaybackStateChanged', [ - args[2], // Processing state - args[3], // Playing - actionBits, // Action bits - args[4], // Position - args[5], // bufferedPosition - args[6] ?? 1.0, // speed - args[7] ?? DateTime.now().millisecondsSinceEpoch, // updateTime - args[9], // repeatMode - args[10], // shuffleMode - ]); - } - - Future setMediaItem(MethodCall call) async { - mediaItem = MediaItem.fromJson(call.arguments); - // This would be how we could pull images out of the cache... But nothing is actually cached on web - final artUri = /* mediaItem.extras['artCacheFile'] ?? */ - mediaItem!.artUri; - - try { - metadata = html.MediaMetadata({ - 'album': mediaItem!.album, - 'title': mediaItem!.title, - 'artist': mediaItem!.artist, - 'artwork': [ - { - 'src': artUri, - 'sizes': '512x512', - } - ], - }); - } catch (e) { - print('Metadata failed $e'); - } - - plugin.clientHandler.invokeMethod('onMediaChanged', [mediaItem!.toJson()]); - } - - Future setQueue(MethodCall call) async { - plugin.clientHandler.invokeMethod('onQueueChanged', [call.arguments]); - } -} diff --git a/audio_service/pubspec.yaml b/audio_service/pubspec.yaml index c952bf75..78cab81e 100644 --- a/audio_service/pubspec.yaml +++ b/audio_service/pubspec.yaml @@ -11,6 +11,8 @@ dependencies: # TODO: change this to hosted once released. audio_service_platform_interface: path: ../audio_service_platform_interface + audio_service_web: + path: ../audio_service_web audio_session: ^0.1.0 rxdart: ^0.26.0 flutter_cache_manager: ^3.0.0-nullsafety.1 @@ -39,5 +41,4 @@ flutter: macos: pluginClass: AudioServicePlugin web: - pluginClass: AudioServicePlugin - fileName: audio_service_web.dart + default_package: audio_service_web diff --git a/audio_service_platform_interface/pubspec.lock b/audio_service_platform_interface/pubspec.lock index d2af39af..b6696730 100644 --- a/audio_service_platform_interface/pubspec.lock +++ b/audio_service_platform_interface/pubspec.lock @@ -239,7 +239,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.0" stack_trace: dependency: transitive description: diff --git a/audio_service_web/.gitignore b/audio_service_web/.gitignore new file mode 100644 index 00000000..e9dc58d3 --- /dev/null +++ b/audio_service_web/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ diff --git a/audio_service_web/.idea/libraries/Dart_SDK.xml b/audio_service_web/.idea/libraries/Dart_SDK.xml new file mode 100644 index 00000000..d62664bf --- /dev/null +++ b/audio_service_web/.idea/libraries/Dart_SDK.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/audio_service_web/.idea/modules.xml b/audio_service_web/.idea/modules.xml new file mode 100644 index 00000000..a29e173e --- /dev/null +++ b/audio_service_web/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/audio_service_web/.idea/runConfigurations/example_lib_main_dart.xml b/audio_service_web/.idea/runConfigurations/example_lib_main_dart.xml new file mode 100644 index 00000000..5fd9159d --- /dev/null +++ b/audio_service_web/.idea/runConfigurations/example_lib_main_dart.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/audio_service_web/.idea/workspace.xml b/audio_service_web/.idea/workspace.xml new file mode 100644 index 00000000..bc0524dc --- /dev/null +++ b/audio_service_web/.idea/workspace.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/audio_service_web/.metadata b/audio_service_web/.metadata new file mode 100644 index 00000000..50b2f551 --- /dev/null +++ b/audio_service_web/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 02d441ea55b328133c266991f43b0a1148edb63f + channel: master + +project_type: plugin diff --git a/audio_service_web/CHANGELOG.md b/audio_service_web/CHANGELOG.md new file mode 100644 index 00000000..41cc7d81 --- /dev/null +++ b/audio_service_web/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/audio_service_web/LICENSE b/audio_service_web/LICENSE new file mode 100644 index 00000000..ba75c69f --- /dev/null +++ b/audio_service_web/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/audio_service_web/README.md b/audio_service_web/README.md new file mode 100644 index 00000000..53ced03e --- /dev/null +++ b/audio_service_web/README.md @@ -0,0 +1,15 @@ +# audio_service_web + +A new flutter plugin project. + +## Getting Started + +This project is a starting point for a Flutter +[plug-in package](https://flutter.dev/developing-packages/), +a specialized package that includes platform-specific implementation code for +Android and/or iOS. + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. + diff --git a/audio_service_web/audio_service_web.iml b/audio_service_web/audio_service_web.iml new file mode 100644 index 00000000..429df7da --- /dev/null +++ b/audio_service_web/audio_service_web.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/audio_service_web/lib/audio_service_web.dart b/audio_service_web/lib/audio_service_web.dart new file mode 100644 index 00000000..b72f9365 --- /dev/null +++ b/audio_service_web/lib/audio_service_web.dart @@ -0,0 +1,160 @@ +import 'dart:async'; +import 'dart:html' as html; + +import 'package:audio_service_platform_interface/audio_service_platform_interface.dart'; +import 'package:audio_service/audio_service.dart'; +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +import 'dart:js' as js; +import 'js/media_session_web.dart'; + +class AudioServiceWeb extends AudioServicePlatform { + static void registerWith(Registrar registrar) { + AudioServicePlatform.instance = AudioServiceWeb(); + } + + AudioHandlerCallbacks? handlerCallbacks; + MediaItem? mediaItem; + + @override + Future configure(ConfigureRequest request) async { + return ConfigureResponse(); + // throw UnimplementedError('configure() has not been implemented.'); + } + + Future setState(SetStateRequest request) async { + print('Setting state'); + final session = html.window.navigator.mediaSession!; + for (final control in request.state.controls) { + try { + switch (control.action) { + case MediaActionMessage.play: + session.setActionHandler( + 'play', + () => handlerCallbacks?.play(PlayRequest()), + ); + break; + case MediaActionMessage.pause: + session.setActionHandler( + 'pause', + () => handlerCallbacks?.pause(PauseRequest()), + ); + break; + case MediaActionMessage.skipToPrevious: + session.setActionHandler( + 'previoustrack', + () => handlerCallbacks?.skipToPrevious(SkipToPreviousRequest()), + ); + break; + case MediaActionMessage.skipToNext: + session.setActionHandler( + 'nexttrack', + () => handlerCallbacks?.skipToNext(SkipToNextRequest()), + ); + break; + // The naming convention here is a bit odd but seekbackward seems more + // analagous to rewind than seekBackward + case MediaActionMessage.rewind: + session.setActionHandler( + 'seekbackward', + () => handlerCallbacks?.rewind(RewindRequest()), + ); + break; + case MediaActionMessage.fastForward: + session.setActionHandler( + 'seekforward', + () => handlerCallbacks?.fastForward(FastForwardRequest()), + ); + break; + case MediaActionMessage.stop: + session.setActionHandler( + 'stop', + () => handlerCallbacks?.stop(StopRequest()), + ); + break; + default: + // no-op + break; + } + } catch (e) {} + for (MediaActionMessage message in request.state.systemActions) { + switch (message) { + case MediaActionMessage.seek: + try { + setActionHandler('seekto', js.allowInterop((ActionResult ev) { + // Chrome uses seconds for whatever reason + handlerCallbacks?.seek(SeekRequest( + position: Duration( + milliseconds: (ev.seekTime * 1000).round(), + ))); + })); + } catch (e) {} + break; + default: + // no-op + break; + } + } + + try { + // Dart also doesn't expose setPositionState + if (mediaItem != null) { + //print( + // 'Setting positionState Duration(${mediaItem!.duration?.inSeconds}), PlaybackRate(${args[6] ?? 1.0}), Position(${Duration(milliseconds: args[4])?.inSeconds})'); + + // Chrome looks for seconds for some reason + setPositionState(PositionState( + duration: (mediaItem!.duration?.inMilliseconds ?? 0) / 1000, + playbackRate: request.state.speed, + position: request.state.updatePosition.inMilliseconds / 1000, + )); + } + } catch (e) { + print(e); + } + } + } + + Future setQueue(SetQueueRequest request) async { + //no-op there is not a queue concept on the web + } + + Future setMediaItem(SetMediaItemRequest request) async { + final mediaItem = request.mediaItem; + final artUri = mediaItem.artUri; + + print('setting media item!'); + + try { + metadata = html.MediaMetadata({ + 'album': mediaItem.album, + 'title': mediaItem.title, + 'artist': mediaItem.artist, + 'artwork': [ + { + 'src': artUri, + 'sizes': '512x512', + } + ], + }); + } catch (e) { + print('Metadata failed $e'); + } + } + + Future stopService(StopServiceRequest request) async { + final session = html.window.navigator.mediaSession!; + session.metadata = null; + mediaItem = null; + + // Not sure if anything needs to happen here + // throw UnimplementedError('stopService() has not been implemented.'); + } + + void setClientCallbacks(AudioClientCallbacks callbacks) {} + + void setHandlerCallbacks(AudioHandlerCallbacks callbacks) { + // Save this here so that we can modify which handlers are set based + // on which actions are enabled + handlerCallbacks = callbacks; + } +} diff --git a/audio_service/lib/js/media_session_web.dart b/audio_service_web/lib/js/media_session_web.dart similarity index 100% rename from audio_service/lib/js/media_session_web.dart rename to audio_service_web/lib/js/media_session_web.dart diff --git a/audio_service_web/pubspec.lock b/audio_service_web/pubspec.lock new file mode 100644 index 00000000..47a2471c --- /dev/null +++ b/audio_service_web/pubspec.lock @@ -0,0 +1,362 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.2" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.5.0" + audio_service: + dependency: "direct main" + description: + path: "../audio_service" + relative: true + source: path + version: "0.16.2" + audio_service_platform_interface: + dependency: "direct main" + description: + path: "../audio_service_platform_interface" + relative: true + source: path + version: "1.0.0" + audio_session: + dependency: "direct main" + description: + name: audio_session + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.15.0" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_cache_manager: + dependency: "direct main" + description: + name: flutter_cache_manager + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.1" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + js: + dependency: "direct main" + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.10" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + path_provider: + dependency: transitive + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.0" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.2" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.1" + rxdart: + dependency: "direct main" + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.26.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + sqflite: + dependency: "direct main" + description: + name: sqflite + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0+3" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0+2" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + synchronized: + dependency: transitive + description: + name: synchronized + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.19" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + uuid: + dependency: transitive + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.3" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.2" +sdks: + dart: ">=2.12.0 <3.0.0" + flutter: ">=1.24.0-10" diff --git a/audio_service_web/pubspec.yaml b/audio_service_web/pubspec.yaml new file mode 100644 index 00000000..9633c74c --- /dev/null +++ b/audio_service_web/pubspec.yaml @@ -0,0 +1,37 @@ +name: audio_service_web +description: Flutter plugin to play audio in the background while the screen is off. +version: 0.16.2 +homepage: https://github.com/ryanheise/audio_service/tree/master/audio_service + +environment: + sdk: '>=2.12.0 <3.0.0' + flutter: ">=1.12.13+hotfix.5" + +dependencies: + # TODO: change this to hosted once released. + audio_service: + path: ../audio_service + audio_service_platform_interface: + path: ../audio_service_platform_interface + audio_session: ^0.1.0 + rxdart: ^0.26.0 + flutter_cache_manager: ^3.0.0-nullsafety.1 + # This sqflite constraint is needed to make the >= 2.0.0 versions of + # flutter_cache_manager compile. + sqflite: ^2.0.0+2 + js: ^0.6.3 + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + +flutter: + plugin: + platforms: + web: + pluginClass: AudioServiceWeb + fileName: audio_service_web.dart diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 00000000..682e40dd --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,488 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "19.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.2" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.5.0" + audio_session: + dependency: "direct main" + description: + name: audio_session + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.0" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "8.0.4" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "3.7.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.15.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.0" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_cache_manager: + dependency: "direct main" + description: + name: flutter_cache_manager + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + flutter_isolate: + dependency: "direct main" + description: + name: flutter_isolate + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.1" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + js: + dependency: "direct main" + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.10" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + mockito: + dependency: "direct dev" + description: + name: mockito + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.3" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + path_provider: + dependency: transitive + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.0" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.2" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + rxdart: + dependency: "direct main" + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.26.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + sqflite: + dependency: transitive + description: + name: sqflite + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0+3" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0+2" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + synchronized: + dependency: transitive + description: + name: synchronized + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.19" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + uuid: + dependency: transitive + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.3" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.2" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" +sdks: + dart: ">=2.12.0 <3.0.0" + flutter: ">=1.24.0-10"