Skip to content

Commit

Permalink
Merge branch 'feature/wasm' into minor
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanheise committed Feb 2, 2025
2 parents 2994f08 + 1e8ad86 commit e5a1e4a
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 140 deletions.
2 changes: 1 addition & 1 deletion audio_service/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Demonstrates how to use the audio_service plugin.
publish_to: 'none'

environment:
sdk: ">=2.14.0 <4.0.0"
sdk: ">=3.3.0 <4.0.0"
flutter: ">=3.0.0"

dependencies:
Expand Down
14 changes: 3 additions & 11 deletions audio_service/example/web/index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<!DOCTYPE html>
<html>
<head>
<base href="$FLUTTER_BASE_HREF">

<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
Expand All @@ -18,16 +20,6 @@
<link rel="manifest" href="manifest.json">
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('flutter_service_worker.js');
});
}
</script>
<script src="main.dart.js" type="application/javascript"></script>
<script src="flutter_bootstrap.js" async></script>
</body>
</html>
6 changes: 3 additions & 3 deletions audio_service/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ topics:
- background

environment:
sdk: ">=2.14.0 <4.0.0"
sdk: ">=3.3.0 <4.0.0"
flutter: ">=3.0.0"

dependencies:
Expand All @@ -31,8 +31,8 @@ dependencies:
# path: audio_service_web

# Use these deps when publishing.
audio_service_platform_interface: ^0.1.2
audio_service_web: ^0.1.3
audio_service_platform_interface: ^0.1.3
audio_service_web: ^0.1.4

audio_session: ^0.1.20
rxdart: '>=0.26.0 <0.29.0'
Expand Down
4 changes: 4 additions & 0 deletions audio_service_web/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.1.4

* Migrated to package:web (@ali2236)

## 0.1.3

* Support rxdart 0.28.x.
Expand Down
115 changes: 66 additions & 49 deletions audio_service_web/lib/audio_service_web.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import 'dart:async';
import 'dart:html' as html;
import 'dart:js' as js;
import 'dart:js_util';
import 'dart:js_interop' as js;
import 'dart:js_interop_unsafe';

import 'package:audio_service_platform_interface/audio_service_platform_interface.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'package:web/web.dart' as web;

import 'js/media_session_web.dart';

class AudioServiceWeb extends AudioServicePlatform {
static void registerWith(Registrar registrar) {
AudioServicePlatform.instance = AudioServiceWeb();
}

web.MediaSession get _mediaSession => web.window.navigator.mediaSession;

final _mediaSessionSupported = _SupportChecker(
() => js.context.hasProperty('MediaSession'),
() => js.globalContext.hasProperty('MediaSession'.toJS).toDart,
"MediaSession is not supported in this browser, so plugin is no-op",
);
final _setPositionStateSupported = _SupportChecker(
() => hasProperty(html.window.navigator.mediaSession!, 'setPositionState'),
() => web.window.navigator.mediaSession
.hasProperty('setPositionState'.toJS)
.toDart,
"MediaSession.setPositionState is not supported in this browser",
);

Expand All @@ -39,60 +44,71 @@ class AudioServiceWeb extends AudioServicePlatform {
final state = request.state;

if (state.processingState == AudioProcessingStateMessage.idle) {
MediaSession.playbackState = MediaSessionPlaybackState.none;
_mediaSession.playbackState = MediaSessionPlaybackState.none;
} else {
if (state.playing) {
MediaSession.playbackState = MediaSessionPlaybackState.playing;
_mediaSession.playbackState = MediaSessionPlaybackState.playing;
} else {
MediaSession.playbackState = MediaSessionPlaybackState.paused;
_mediaSession.playbackState = MediaSessionPlaybackState.paused;
}
}

for (final control in state.controls) {
switch (control.action) {
case MediaActionMessage.play:
MediaSession.setActionHandler(
_mediaSession.setActionHandler(
MediaSessionActions.play,
(details) => handlerCallbacks?.play(const PlayRequest()),
((MediaSessionActionDetails details) {
handlerCallbacks?.play(const PlayRequest());
}).toJS,
);
break;
case MediaActionMessage.pause:
MediaSession.setActionHandler(
_mediaSession.setActionHandler(
MediaSessionActions.pause,
(details) => handlerCallbacks?.pause(const PauseRequest()),
((MediaSessionActionDetails details) {
handlerCallbacks?.pause(const PauseRequest());
}).toJS,
);
break;
case MediaActionMessage.skipToPrevious:
MediaSession.setActionHandler(
_mediaSession.setActionHandler(
MediaSessionActions.previoustrack,
(details) =>
handlerCallbacks?.skipToPrevious(const SkipToPreviousRequest()),
((MediaSessionActionDetails details) {
handlerCallbacks?.skipToPrevious(const SkipToPreviousRequest());
}).toJS,
);
break;
case MediaActionMessage.skipToNext:
MediaSession.setActionHandler(
_mediaSession.setActionHandler(
MediaSessionActions.nexttrack,
(details) =>
handlerCallbacks?.skipToNext(const SkipToNextRequest()),
((MediaSessionActionDetails details) {
handlerCallbacks?.skipToNext(const SkipToNextRequest());
}).toJS,
);
break;
case MediaActionMessage.rewind:
MediaSession.setActionHandler(
_mediaSession.setActionHandler(
MediaSessionActions.seekbackward,
(details) => handlerCallbacks?.rewind(const RewindRequest()),
((MediaSessionActionDetails details) {
handlerCallbacks?.rewind(const RewindRequest());
}).toJS,
);
break;
case MediaActionMessage.fastForward:
MediaSession.setActionHandler(
_mediaSession.setActionHandler(
MediaSessionActions.seekforward,
(details) =>
handlerCallbacks?.fastForward(const FastForwardRequest()),
((MediaSessionActionDetails details) {
handlerCallbacks?.fastForward(const FastForwardRequest());
}).toJS,
);
break;
case MediaActionMessage.stop:
MediaSession.setActionHandler(
_mediaSession.setActionHandler(
MediaSessionActions.stop,
(details) => handlerCallbacks?.stop(const StopRequest()),
((MediaSessionActionDetails details) {
handlerCallbacks?.stop(const StopRequest());
}).toJS,
);
break;
default:
Expand All @@ -104,14 +120,15 @@ class AudioServiceWeb extends AudioServicePlatform {
for (final message in state.systemActions) {
switch (message) {
case MediaActionMessage.seek:
MediaSession.setActionHandler('seekto',
(MediaSessionActionDetails details) {
// Browsers use seconds
handlerCallbacks?.seek(SeekRequest(
position:
Duration(milliseconds: (details.seekTime * 1000).round()),
));
});
_mediaSession.setActionHandler(
'seekto',
((MediaSessionActionDetails details) {
// Browsers use seconds
handlerCallbacks?.seek(SeekRequest(
position: Duration(
milliseconds: (details.seekTime! * 1000).round()),
));
}).toJS);
break;
default:
// no-op
Expand All @@ -128,7 +145,7 @@ class AudioServiceWeb extends AudioServicePlatform {
final position = _minDuration(state.updatePosition, duration);

// Browsers expect for seconds
MediaSession.setPositionState(MediaSessionPositionState(
_mediaSession.setPositionState(web.MediaPositionState(
duration: duration.inMilliseconds / 1000,
playbackRate: state.speed,
position: position.inMilliseconds / 1000,
Expand All @@ -147,30 +164,29 @@ class AudioServiceWeb extends AudioServicePlatform {
return;
}
mediaItem = request.mediaItem;
final artist = mediaItem!.artist;
final album = mediaItem!.album;
final artist = mediaItem!.artist ?? '';
final album = mediaItem!.album ?? '';
final artUri = mediaItem!.artUri;

MediaSession.metadata = html.MediaMetadata(<String, dynamic>{
'title': mediaItem!.title,
if (artist != null) 'artist': artist,
if (album != null) 'album': album,
'artwork': [
{
'src': artUri,
'sizes': '512x512',
}
],
});
_mediaSession.metadata = web.MediaMetadata(
web.MediaMetadataInit(
title: mediaItem!.title,
artist: artist,
album: album,
artwork: [
if (artUri != null)
web.MediaImage(src: artUri.toString(), sizes: '512x512'),
].toJS,
),
);
}

@override
Future<void> stopService(StopServiceRequest request) async {
if (!_mediaSessionSupported.check()) {
return;
}
final session = html.window.navigator.mediaSession!;
session.metadata = null;
_mediaSession.metadata = null;
mediaItem = null;
}

Expand All @@ -193,6 +209,7 @@ class _SupportChecker {
_SupportChecker(this._checkCallback, this._warningMessage);

bool _logged = false;

bool check() {
final result = _checkCallback();
if (!_logged && !result) {
Expand Down
76 changes: 5 additions & 71 deletions audio_service_web/lib/js/media_session_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,7 @@
@JS()
library media_session_web;

import 'dart:js' as js;
import 'dart:html' as html;
import 'package:js/js.dart';

/// The interface to the Media Session API which allows a web page
/// to provide custom behaviors for standard media playback interactions,
/// and to report metadata that can be sent by the user agent
/// to the device or operating system for presentation in standardized
/// user interface elements.
@JS('navigator.mediaSession')
class MediaSession {
/// Returns an instance of [html.MediaMetadata], which contains rich media
/// metadata for display in a platform UI.
@JS('metadata')
external static html.MediaMetadata metadata;

/// Indicates whether the current media session is playing.
///
/// See [MediaSessionPlaybackState] for possible values.
@JS('playbackState')
external static String playbackState;

/// Sets an action handler for a media session action, such as play or pause.
static void setActionHandler(
String action,
MediaSessionActionHandler callback,
) =>
_setActionHandler(action, js.allowInterop(callback));

@JS('setActionHandler')
external static void _setActionHandler(
String action,
MediaSessionActionHandler callback,
);

/// Sets the current playback position and speed of the media currently being presented.
@JS('setPositionState')
external static void setPositionState(MediaSessionPositionState? state);
}
import 'dart:js_interop';

/// Media session playback state types.
///
Expand Down Expand Up @@ -90,7 +52,7 @@ typedef MediaSessionActionHandler = dynamic Function(MediaSessionActionDetails);
/// The dictionary parameter for [MediaSessionActionHandler] callback.
@JS()
@anonymous
class MediaSessionActionDetails {
extension type MediaSessionActionDetails._(JSObject _) implements JSObject {
/// An action type string taken from the [MediaActions], indicating which
/// type of action needs to be performed.
external String get action;
Expand All @@ -105,21 +67,21 @@ class MediaSessionActionDetails {
/// This property can be used to indicate that you should use the shortest possible
/// method to seek the media. This property is not included on the final action in
/// the seek sequence in this situation.
external bool get fastSeek;
external bool? get fastSeek;

/// If the action is either `seekforward` or `seekbackward`
/// and this property is present, it is a floating point value which indicates
/// the seek interval.
///
/// If this property isn't present, those actions should choose a reasonable
/// default interval.
external double get seekOffset;
external double? get seekOffset;

/// If the action is `seekto`, this property is present and
/// indicates the absolute time within the media to move the playback position to.
///
/// This property is not present for other action types.
external double get seekTime;
external double? get seekTime;

/// Creates the details.
external factory MediaSessionActionDetails({
Expand All @@ -129,31 +91,3 @@ class MediaSessionActionDetails {
double? seekTime,
});
}

/// A representation of the current playback.
///
/// The dictionary parameter for [MediaSession.setPositionState].
@JS()
@anonymous
class MediaSessionPositionState {
/// Duration in seconds.
external double get duration;

/// Playback rate.
///
/// Can be positive to represent forward playback or negative to
/// represent backwards playback.
///
/// Cannot be zero.
external double get playbackRate;

/// Position in seconds.
external double get position;

/// Creates the position state.
external factory MediaSessionPositionState({
double? duration,
double? playbackRate,
double? position,
});
}
Loading

0 comments on commit e5a1e4a

Please sign in to comment.