feat(playback): integrate android, ios, macos with JustAudio

This commit is contained in:
Kingkor Roy Tirtho 2023-05-05 20:03:06 +06:00
parent cdb32685e4
commit d487fe5563
6 changed files with 128 additions and 113 deletions

View File

@ -1,53 +1,71 @@
import 'dart:async'; import 'dart:async';
import 'package:audioplayers/audioplayers.dart' as ap; import 'package:audioplayers/audioplayers.dart' as ap;
import 'package:assets_audio_player/assets_audio_player.dart' as aap; import 'package:just_audio/just_audio.dart' as ja;
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
final audioPlayer = SpotubeAudioPlayer(); final audioPlayer = SpotubeAudioPlayer();
enum PlayerState { enum AudioPlaybackState {
playing, playing,
paused, paused,
completed, completed,
buffering, buffering,
stopped; stopped;
static PlayerState fromApPlayerState(ap.PlayerState state) { static AudioPlaybackState fromApPlayerState(ap.PlayerState state) {
switch (state) { switch (state) {
case ap.PlayerState.playing: case ap.PlayerState.playing:
return PlayerState.playing; return AudioPlaybackState.playing;
case ap.PlayerState.paused: case ap.PlayerState.paused:
return PlayerState.paused; return AudioPlaybackState.paused;
case ap.PlayerState.stopped: case ap.PlayerState.stopped:
return PlayerState.stopped; return AudioPlaybackState.stopped;
case ap.PlayerState.completed: case ap.PlayerState.completed:
return PlayerState.completed; return AudioPlaybackState.completed;
} }
} }
static PlayerState fromAapPlayerState(aap.PlayerState state) { static AudioPlaybackState fromJaPlayerState(ja.PlayerState state) {
switch (state) { if (state.playing) {
case aap.PlayerState.play: return AudioPlaybackState.playing;
return PlayerState.playing; }
case aap.PlayerState.pause:
return PlayerState.paused; switch (state.processingState) {
case aap.PlayerState.stop: case ja.ProcessingState.idle:
return PlayerState.stopped; return AudioPlaybackState.stopped;
case ja.ProcessingState.ready:
return AudioPlaybackState.paused;
case ja.ProcessingState.completed:
return AudioPlaybackState.completed;
case ja.ProcessingState.loading:
case ja.ProcessingState.buffering:
return AudioPlaybackState.buffering;
} }
} }
// static PlayerState fromAapPlayerState(aap.PlayerState state) {
// switch (state) {
// case aap.PlayerState.play:
// return PlayerState.playing;
// case aap.PlayerState.pause:
// return PlayerState.paused;
// case aap.PlayerState.stop:
// return PlayerState.stopped;
// }
// }
ap.PlayerState get asAudioPlayerPlayerState { ap.PlayerState get asAudioPlayerPlayerState {
switch (this) { switch (this) {
case PlayerState.playing: case AudioPlaybackState.playing:
return ap.PlayerState.playing; return ap.PlayerState.playing;
case PlayerState.paused: case AudioPlaybackState.paused:
return ap.PlayerState.paused; return ap.PlayerState.paused;
case PlayerState.stopped: case AudioPlaybackState.stopped:
return ap.PlayerState.stopped; return ap.PlayerState.stopped;
case PlayerState.completed: case AudioPlaybackState.completed:
return ap.PlayerState.completed; return ap.PlayerState.completed;
case PlayerState.buffering: case AudioPlaybackState.buffering:
return ap.PlayerState.paused; return ap.PlayerState.paused;
} }
} }
@ -55,12 +73,11 @@ enum PlayerState {
class SpotubeAudioPlayer { class SpotubeAudioPlayer {
final ap.AudioPlayer? _audioPlayer; final ap.AudioPlayer? _audioPlayer;
final aap.AssetsAudioPlayer? _assetsAudioPlayer; final ja.AudioPlayer? _justAudio;
SpotubeAudioPlayer() SpotubeAudioPlayer()
: _audioPlayer = apSupportedPlatform ? ap.AudioPlayer() : null, : _audioPlayer = apSupportedPlatform ? ap.AudioPlayer() : null,
_assetsAudioPlayer = _justAudio = !apSupportedPlatform ? ja.AudioPlayer() : null;
!apSupportedPlatform ? aap.AssetsAudioPlayer.newPlayer() : null;
/// Whether the current platform supports the audioplayers plugin /// Whether the current platform supports the audioplayers plugin
static final bool apSupportedPlatform = static final bool apSupportedPlatform =
@ -71,9 +88,9 @@ class SpotubeAudioPlayer {
if (apSupportedPlatform) { if (apSupportedPlatform) {
return _audioPlayer!.onDurationChanged.asBroadcastStream(); return _audioPlayer!.onDurationChanged.asBroadcastStream();
} else { } else {
return _assetsAudioPlayer!.onReadyToPlay return _justAudio!.durationStream
.where((event) => event != null) .where((event) => event != null)
.map((event) => event!.duration) .map((event) => event!)
.asBroadcastStream(); .asBroadcastStream();
} }
} }
@ -82,7 +99,7 @@ class SpotubeAudioPlayer {
if (apSupportedPlatform) { if (apSupportedPlatform) {
return _audioPlayer!.onPositionChanged.asBroadcastStream(); return _audioPlayer!.onPositionChanged.asBroadcastStream();
} else { } else {
return _assetsAudioPlayer!.currentPosition.asBroadcastStream(); return _justAudio!.positionStream.asBroadcastStream();
} }
} }
@ -91,7 +108,7 @@ class SpotubeAudioPlayer {
// audioplayers doesn't have the capability to get buffered position // audioplayers doesn't have the capability to get buffered position
return const Stream<Duration>.empty().asBroadcastStream(); return const Stream<Duration>.empty().asBroadcastStream();
} else { } else {
return const Stream<Duration>.empty().asBroadcastStream(); return _justAudio!.bufferedPositionStream.asBroadcastStream();
} }
} }
@ -99,20 +116,10 @@ class SpotubeAudioPlayer {
if (apSupportedPlatform) { if (apSupportedPlatform) {
return _audioPlayer!.onPlayerComplete.asBroadcastStream(); return _audioPlayer!.onPlayerComplete.asBroadcastStream();
} else { } else {
int lastValue = 0; return _justAudio!.playerStateStream
return positionStream.where( .where(
(pos) { (event) => event.processingState == ja.ProcessingState.completed)
final posS = pos.inSeconds; .asBroadcastStream();
final duration = _assetsAudioPlayer
?.current.valueOrNull?.audio.duration.inSeconds ??
0;
final isComplete =
posS > 0 && duration > 0 && posS == duration && posS != lastValue;
if (isComplete) lastValue = posS;
return isComplete;
},
).asBroadcastStream();
} }
} }
@ -122,7 +129,7 @@ class SpotubeAudioPlayer {
return state == ap.PlayerState.playing; return state == ap.PlayerState.playing;
}).asBroadcastStream(); }).asBroadcastStream();
} else { } else {
return _assetsAudioPlayer!.isPlaying.asBroadcastStream(); return _justAudio!.playingStream;
} }
} }
@ -130,18 +137,24 @@ class SpotubeAudioPlayer {
if (apSupportedPlatform) { if (apSupportedPlatform) {
return Stream.value(false).asBroadcastStream(); return Stream.value(false).asBroadcastStream();
} else { } else {
return _assetsAudioPlayer!.isBuffering.asBroadcastStream(); return _justAudio!.playerStateStream
.map(
(event) =>
event.processingState == ja.ProcessingState.buffering ||
event.processingState == ja.ProcessingState.loading,
)
.asBroadcastStream();
} }
} }
Stream<PlayerState> get playerStateStream { Stream<AudioPlaybackState> get playerStateStream {
if (apSupportedPlatform) { if (apSupportedPlatform) {
return _audioPlayer!.onPlayerStateChanged return _audioPlayer!.onPlayerStateChanged
.map((state) => PlayerState.fromApPlayerState(state)) .map((state) => AudioPlaybackState.fromApPlayerState(state))
.asBroadcastStream(); .asBroadcastStream();
} else { } else {
return _assetsAudioPlayer!.playerState return _justAudio!.playerStateStream
.map(PlayerState.fromAapPlayerState) .map(AudioPlaybackState.fromJaPlayerState)
.asBroadcastStream(); .asBroadcastStream();
} }
} }
@ -152,7 +165,7 @@ class SpotubeAudioPlayer {
if (apSupportedPlatform) { if (apSupportedPlatform) {
return await _audioPlayer!.getDuration(); return await _audioPlayer!.getDuration();
} else { } else {
return _assetsAudioPlayer!.current.valueOrNull?.audio.duration; return _justAudio!.duration;
} }
} }
@ -160,7 +173,7 @@ class SpotubeAudioPlayer {
if (apSupportedPlatform) { if (apSupportedPlatform) {
return await _audioPlayer!.getCurrentPosition(); return await _audioPlayer!.getCurrentPosition();
} else { } else {
return _assetsAudioPlayer!.currentPosition.valueOrNull; return _justAudio!.position;
} }
} }
@ -177,7 +190,7 @@ class SpotubeAudioPlayer {
if (apSupportedPlatform) { if (apSupportedPlatform) {
return _audioPlayer!.source != null; return _audioPlayer!.source != null;
} else { } else {
return _assetsAudioPlayer!.current.valueOrNull != null; return _justAudio!.audioSource != null;
} }
} }
@ -186,7 +199,7 @@ class SpotubeAudioPlayer {
if (apSupportedPlatform) { if (apSupportedPlatform) {
return _audioPlayer!.state == ap.PlayerState.playing; return _audioPlayer!.state == ap.PlayerState.playing;
} else { } else {
return _assetsAudioPlayer!.isPlaying.valueOrNull ?? false; return _justAudio!.playing;
} }
} }
@ -194,7 +207,7 @@ class SpotubeAudioPlayer {
if (apSupportedPlatform) { if (apSupportedPlatform) {
return _audioPlayer!.state == ap.PlayerState.paused; return _audioPlayer!.state == ap.PlayerState.paused;
} else { } else {
return !isPlaying && hasSource; return !isPlaying;
} }
} }
@ -202,7 +215,7 @@ class SpotubeAudioPlayer {
if (apSupportedPlatform) { if (apSupportedPlatform) {
return _audioPlayer!.state == ap.PlayerState.stopped; return _audioPlayer!.state == ap.PlayerState.stopped;
} else { } else {
return !isPlaying && !hasSource; return _justAudio!.processingState == ja.ProcessingState.idle;
} }
} }
@ -210,7 +223,7 @@ class SpotubeAudioPlayer {
if (apSupportedPlatform) { if (apSupportedPlatform) {
return _audioPlayer!.state == ap.PlayerState.completed; return _audioPlayer!.state == ap.PlayerState.completed;
} else { } else {
return !isPlaying && hasSource && await position == await duration; return _justAudio!.processingState == ja.ProcessingState.completed;
} }
} }
@ -219,7 +232,8 @@ class SpotubeAudioPlayer {
// audioplayers doesn't have the capability to get buffering state // audioplayers doesn't have the capability to get buffering state
return false; return false;
} else { } else {
return _assetsAudioPlayer!.isBuffering.valueOrNull ?? false; return _justAudio!.processingState == ja.ProcessingState.buffering ||
_justAudio!.processingState == ja.ProcessingState.loading;
} }
} }
@ -232,9 +246,9 @@ class SpotubeAudioPlayer {
} }
} else { } else {
if (url.startsWith("https")) { if (url.startsWith("https")) {
return aap.Audio.network(url); return ja.AudioSource.uri(Uri.parse(url));
} else { } else {
return aap.Audio.file(url); return ja.AudioSource.file(url);
} }
} }
} }
@ -254,54 +268,55 @@ class SpotubeAudioPlayer {
if (apSupportedPlatform && urlType is ap.Source) { if (apSupportedPlatform && urlType is ap.Source) {
await _audioPlayer?.play(urlType); await _audioPlayer?.play(urlType);
} else { } else {
await _assetsAudioPlayer?.stop(); if (_justAudio?.audioSource is ja.ProgressiveAudioSource &&
await _assetsAudioPlayer?.open( (_justAudio?.audioSource as ja.ProgressiveAudioSource)
urlType as aap.Playable, .uri
autoStart: true, .toString() ==
audioFocusStrategy: const aap.AudioFocusStrategy.request( url) {
resumeAfterInterruption: true, await _justAudio?.play();
), } else {
loopMode: aap.LoopMode.none, await _justAudio?.stop();
playInBackground: aap.PlayInBackground.enabled, await _justAudio?.setAudioSource(
headPhoneStrategy: aap.HeadPhoneStrategy.pauseOnUnplugPlayOnPlug, urlType as ja.AudioSource,
showNotification: false, preload: true,
respectSilentMode: true, );
); await _justAudio?.play();
}
} }
} }
Future<void> pause() async { Future<void> pause() async {
await _audioPlayer?.pause(); await _audioPlayer?.pause();
await _assetsAudioPlayer?.pause(); await _justAudio?.pause();
} }
Future<void> resume() async { Future<void> resume() async {
await _audioPlayer?.resume(); await _audioPlayer?.resume();
await _assetsAudioPlayer?.play(); await _justAudio?.play();
} }
Future<void> stop() async { Future<void> stop() async {
await _audioPlayer?.stop(); await _audioPlayer?.stop();
await _assetsAudioPlayer?.stop(); await _justAudio?.stop();
} }
Future<void> seek(Duration position) async { Future<void> seek(Duration position) async {
await _audioPlayer?.seek(position); await _audioPlayer?.seek(position);
await _assetsAudioPlayer?.seek(position); await _justAudio?.seek(position);
} }
Future<void> setVolume(double volume) async { Future<void> setVolume(double volume) async {
await _audioPlayer?.setVolume(volume); await _audioPlayer?.setVolume(volume);
await _assetsAudioPlayer?.setVolume(volume); await _justAudio?.setVolume(volume);
} }
Future<void> setSpeed(double speed) async { Future<void> setSpeed(double speed) async {
await _audioPlayer?.setPlaybackRate(speed); await _audioPlayer?.setPlaybackRate(speed);
await _assetsAudioPlayer?.setPlaySpeed(speed); await _justAudio?.setSpeed(speed);
} }
Future<void> dispose() async { Future<void> dispose() async {
await _audioPlayer?.dispose(); await _audioPlayer?.dispose();
await _assetsAudioPlayer?.dispose(); await _justAudio?.dispose();
} }
} }

View File

@ -62,15 +62,15 @@ class LinuxAudioService {
final playerStateStream = final playerStateStream =
audioPlayer.playerStateStream.listen((state) async { audioPlayer.playerStateStream.listen((state) async {
switch (state) { switch (state) {
case PlayerState.buffering: case AudioPlaybackState.buffering:
case PlayerState.playing: case AudioPlaybackState.playing:
mpris.playbackStatus = MPRISPlaybackStatus.playing; mpris.playbackStatus = MPRISPlaybackStatus.playing;
break; break;
case PlayerState.paused: case AudioPlaybackState.paused:
mpris.playbackStatus = MPRISPlaybackStatus.paused; mpris.playbackStatus = MPRISPlaybackStatus.paused;
break; break;
case PlayerState.stopped: case AudioPlaybackState.stopped:
case PlayerState.completed: case AudioPlaybackState.completed:
mpris.playbackStatus = MPRISPlaybackStatus.stopped; mpris.playbackStatus = MPRISPlaybackStatus.stopped;
break; break;
default: default:

View File

@ -42,17 +42,17 @@ class WindowsAudioService {
final playerStateStream = final playerStateStream =
audioPlayer.playerStateStream.listen((state) async { audioPlayer.playerStateStream.listen((state) async {
switch (state) { switch (state) {
case PlayerState.playing: case AudioPlaybackState.playing:
await smtc.setPlaybackStatus(PlaybackStatus.Playing); await smtc.setPlaybackStatus(PlaybackStatus.Playing);
break; break;
case PlayerState.paused: case AudioPlaybackState.paused:
await smtc.setPlaybackStatus(PlaybackStatus.Paused); await smtc.setPlaybackStatus(PlaybackStatus.Paused);
break; break;
case PlayerState.stopped: case AudioPlaybackState.stopped:
await smtc.setPlaybackStatus(PlaybackStatus.Stopped); await smtc.setPlaybackStatus(PlaybackStatus.Stopped);
await smtc.disableSmtc(); await smtc.disableSmtc();
break; break;
case PlayerState.completed: case AudioPlaybackState.completed:
await smtc.setPlaybackStatus(PlaybackStatus.Changing); await smtc.setPlaybackStatus(PlaybackStatus.Changing);
await smtc.disableSmtc(); await smtc.disableSmtc();
break; break;

View File

@ -5,14 +5,13 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import assets_audio_player
import assets_audio_player_web
import audio_service import audio_service
import audio_session import audio_session
import audioplayers_darwin import audioplayers_darwin
import catcher import catcher
import device_info_plus import device_info_plus
import flutter_secure_storage_macos import flutter_secure_storage_macos
import just_audio
import local_notifier import local_notifier
import package_info_plus import package_info_plus
import path_provider_foundation import path_provider_foundation
@ -26,14 +25,13 @@ import window_manager
import window_size import window_size
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AssetsAudioPlayerPlugin.register(with: registry.registrar(forPlugin: "AssetsAudioPlayerPlugin"))
AssetsAudioPlayerWebPlugin.register(with: registry.registrar(forPlugin: "AssetsAudioPlayerWebPlugin"))
AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin")) AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin"))
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
CatcherPlugin.register(with: registry.registrar(forPlugin: "CatcherPlugin")) CatcherPlugin.register(with: registry.registrar(forPlugin: "CatcherPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin")) LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))

View File

@ -97,23 +97,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.3.2"
assets_audio_player:
dependency: "direct main"
description:
path: "."
ref: "4cbd4bc4b7be8a329effbb52c5b3f5d3ab042b35"
resolved-ref: "4cbd4bc4b7be8a329effbb52c5b3f5d3ab042b35"
url: "https://github.com/mt633/Flutter-AssetsAudioPlayer"
source: git
version: "3.0.6"
assets_audio_player_web:
dependency: transitive
description:
name: assets_audio_player_web
sha256: "4575ec40033d818ff022d48f7d46e24c01bc632bd1cd36a4f8b58b38e9aa4a81"
url: "https://pub.dev"
source: hosted
version: "3.0.6"
async: async:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1015,6 +998,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.6.1" version: "6.6.1"
just_audio:
dependency: "direct main"
description:
name: just_audio
sha256: "7e6d31508dacd01a066e3889caf6282e5f1eb60707c230203b21a83af5c55586"
url: "https://pub.dev"
source: hosted
version: "0.9.32"
just_audio_platform_interface:
dependency: transitive
description:
name: just_audio_platform_interface
sha256: eff112d5138bea3ba544b6338b1e0537a32b5e1425e4d0dc38f732771cda7c84
url: "https://pub.dev"
source: hosted
version: "4.2.0"
just_audio_web:
dependency: transitive
description:
name: just_audio_web
sha256: "89d8db6f19f3821bb6bf908c4bfb846079afb2ab575b783d781a6bf119e3abaf"
url: "https://pub.dev"
source: hosted
version: "0.4.7"
lints: lints:
dependency: transitive dependency: transitive
description: description:

View File

@ -10,12 +10,6 @@ environment:
dependencies: dependencies:
args: ^2.3.2 args: ^2.3.2
# Patch for the uncontrollable shadow instance of ExoPlayer running in bg
# https://github.com/florent37/Flutter-AssetsAudioPlayer/issues/765
assets_audio_player:
git:
url: https://github.com/mt633/Flutter-AssetsAudioPlayer
ref: 4cbd4bc4b7be8a329effbb52c5b3f5d3ab042b35
async: ^2.9.0 async: ^2.9.0
audio_service: ^0.18.9 audio_service: ^0.18.9
audio_session: ^0.1.13 audio_session: ^0.1.13
@ -61,6 +55,7 @@ dependencies:
introduction_screen: ^3.0.2 introduction_screen: ^3.0.2
json_annotation: ^4.8.0 json_annotation: ^4.8.0
json_serializable: ^6.6.0 json_serializable: ^6.6.0
just_audio: ^0.9.32
logger: ^1.1.0 logger: ^1.1.0
metadata_god: ^0.4.1 metadata_god: ^0.4.1
mime: ^1.0.2 mime: ^1.0.2