mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
feat(playback): use assets_audio_player to fix macos double duration problems and android high loading latency
This commit is contained in:
parent
be91e33828
commit
1fff0f1bd0
@ -28,7 +28,7 @@ class PlayPauseAction extends Action<PlayPauseIntent> {
|
|||||||
if (playlist == null) {
|
if (playlist == null) {
|
||||||
return null;
|
return null;
|
||||||
} else if (!audioPlayer.isPlaying) {
|
} else if (!audioPlayer.isPlaying) {
|
||||||
if (audioPlayer.hasSource && !audioPlayer.isCompleted) {
|
if (audioPlayer.hasSource && !await audioPlayer.isCompleted) {
|
||||||
await playlistNotifier.resume();
|
await playlistNotifier.resume();
|
||||||
} else {
|
} else {
|
||||||
await playlistNotifier.play();
|
await playlistNotifier.play();
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
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:flutter_desktop_tools/flutter_desktop_tools.dart';
|
||||||
|
|
||||||
final audioPlayer = SpotubeAudioPlayer();
|
final audioPlayer = SpotubeAudioPlayer();
|
||||||
|
|
||||||
@ -24,6 +26,17 @@ enum PlayerState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 PlayerState.playing:
|
||||||
@ -42,19 +55,26 @@ enum PlayerState {
|
|||||||
|
|
||||||
class SpotubeAudioPlayer {
|
class SpotubeAudioPlayer {
|
||||||
final ap.AudioPlayer? _audioPlayer;
|
final ap.AudioPlayer? _audioPlayer;
|
||||||
|
final aap.AssetsAudioPlayer? _assetsAudioPlayer;
|
||||||
|
|
||||||
SpotubeAudioPlayer()
|
SpotubeAudioPlayer()
|
||||||
: _audioPlayer = apSupportedPlatform ? ap.AudioPlayer() : null;
|
: _audioPlayer = apSupportedPlatform ? ap.AudioPlayer() : null,
|
||||||
|
_assetsAudioPlayer =
|
||||||
|
!apSupportedPlatform ? aap.AssetsAudioPlayer.newPlayer() : null;
|
||||||
|
|
||||||
/// Whether the current platform supports the audioplayers plugin
|
/// Whether the current platform supports the audioplayers plugin
|
||||||
static const bool apSupportedPlatform = true;
|
static final bool apSupportedPlatform =
|
||||||
|
DesktopTools.platform.isWindows || DesktopTools.platform.isLinux;
|
||||||
|
|
||||||
// stream getters
|
// stream getters
|
||||||
Stream<Duration> get durationStream {
|
Stream<Duration> get durationStream {
|
||||||
if (apSupportedPlatform) {
|
if (apSupportedPlatform) {
|
||||||
return _audioPlayer!.onDurationChanged.asBroadcastStream();
|
return _audioPlayer!.onDurationChanged.asBroadcastStream();
|
||||||
} else {
|
} else {
|
||||||
throw UnimplementedError();
|
return _assetsAudioPlayer!.onReadyToPlay
|
||||||
|
.where((event) => event != null)
|
||||||
|
.map((event) => event!.duration)
|
||||||
|
.asBroadcastStream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +82,7 @@ class SpotubeAudioPlayer {
|
|||||||
if (apSupportedPlatform) {
|
if (apSupportedPlatform) {
|
||||||
return _audioPlayer!.onPositionChanged.asBroadcastStream();
|
return _audioPlayer!.onPositionChanged.asBroadcastStream();
|
||||||
} else {
|
} else {
|
||||||
throw UnimplementedError();
|
return _assetsAudioPlayer!.currentPosition.asBroadcastStream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +91,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 {
|
||||||
throw UnimplementedError();
|
return const Stream<Duration>.empty().asBroadcastStream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +99,20 @@ class SpotubeAudioPlayer {
|
|||||||
if (apSupportedPlatform) {
|
if (apSupportedPlatform) {
|
||||||
return _audioPlayer!.onPlayerComplete.asBroadcastStream();
|
return _audioPlayer!.onPlayerComplete.asBroadcastStream();
|
||||||
} else {
|
} else {
|
||||||
throw UnimplementedError();
|
int lastValue = 0;
|
||||||
|
return positionStream.where(
|
||||||
|
(pos) {
|
||||||
|
final posS = pos.inSeconds;
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +122,7 @@ class SpotubeAudioPlayer {
|
|||||||
return state == ap.PlayerState.playing;
|
return state == ap.PlayerState.playing;
|
||||||
}).asBroadcastStream();
|
}).asBroadcastStream();
|
||||||
} else {
|
} else {
|
||||||
throw UnimplementedError();
|
return _assetsAudioPlayer!.isPlaying.asBroadcastStream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,14 +130,21 @@ class SpotubeAudioPlayer {
|
|||||||
if (apSupportedPlatform) {
|
if (apSupportedPlatform) {
|
||||||
return Stream.value(false).asBroadcastStream();
|
return Stream.value(false).asBroadcastStream();
|
||||||
} else {
|
} else {
|
||||||
throw UnimplementedError();
|
return _assetsAudioPlayer!.isBuffering.asBroadcastStream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<PlayerState> get playerStateStream =>
|
Stream<PlayerState> get playerStateStream {
|
||||||
_audioPlayer!.onPlayerStateChanged
|
if (apSupportedPlatform) {
|
||||||
|
return _audioPlayer!.onPlayerStateChanged
|
||||||
.map((state) => PlayerState.fromApPlayerState(state))
|
.map((state) => PlayerState.fromApPlayerState(state))
|
||||||
.asBroadcastStream();
|
.asBroadcastStream();
|
||||||
|
} else {
|
||||||
|
return _assetsAudioPlayer!.playerState
|
||||||
|
.map(PlayerState.fromAapPlayerState)
|
||||||
|
.asBroadcastStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// regular info getter
|
// regular info getter
|
||||||
|
|
||||||
@ -112,7 +152,7 @@ class SpotubeAudioPlayer {
|
|||||||
if (apSupportedPlatform) {
|
if (apSupportedPlatform) {
|
||||||
return await _audioPlayer!.getDuration();
|
return await _audioPlayer!.getDuration();
|
||||||
} else {
|
} else {
|
||||||
throw UnimplementedError();
|
return _assetsAudioPlayer!.current.valueOrNull?.audio.duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +160,7 @@ class SpotubeAudioPlayer {
|
|||||||
if (apSupportedPlatform) {
|
if (apSupportedPlatform) {
|
||||||
return await _audioPlayer!.getCurrentPosition();
|
return await _audioPlayer!.getCurrentPosition();
|
||||||
} else {
|
} else {
|
||||||
throw UnimplementedError();
|
return _assetsAudioPlayer!.currentPosition.valueOrNull;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +169,7 @@ class SpotubeAudioPlayer {
|
|||||||
// audioplayers doesn't have the capability to get buffered position
|
// audioplayers doesn't have the capability to get buffered position
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
throw UnimplementedError();
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +177,7 @@ class SpotubeAudioPlayer {
|
|||||||
if (apSupportedPlatform) {
|
if (apSupportedPlatform) {
|
||||||
return _audioPlayer!.source != null;
|
return _audioPlayer!.source != null;
|
||||||
} else {
|
} else {
|
||||||
throw UnimplementedError();
|
return _assetsAudioPlayer!.current.valueOrNull != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +186,7 @@ class SpotubeAudioPlayer {
|
|||||||
if (apSupportedPlatform) {
|
if (apSupportedPlatform) {
|
||||||
return _audioPlayer!.state == ap.PlayerState.playing;
|
return _audioPlayer!.state == ap.PlayerState.playing;
|
||||||
} else {
|
} else {
|
||||||
throw UnimplementedError();
|
return _assetsAudioPlayer!.isPlaying.valueOrNull ?? false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +194,7 @@ class SpotubeAudioPlayer {
|
|||||||
if (apSupportedPlatform) {
|
if (apSupportedPlatform) {
|
||||||
return _audioPlayer!.state == ap.PlayerState.paused;
|
return _audioPlayer!.state == ap.PlayerState.paused;
|
||||||
} else {
|
} else {
|
||||||
throw UnimplementedError();
|
return !isPlaying && hasSource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,15 +202,15 @@ class SpotubeAudioPlayer {
|
|||||||
if (apSupportedPlatform) {
|
if (apSupportedPlatform) {
|
||||||
return _audioPlayer!.state == ap.PlayerState.stopped;
|
return _audioPlayer!.state == ap.PlayerState.stopped;
|
||||||
} else {
|
} else {
|
||||||
throw UnimplementedError();
|
return !isPlaying && !hasSource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get isCompleted {
|
Future<bool> get isCompleted async {
|
||||||
if (apSupportedPlatform) {
|
if (apSupportedPlatform) {
|
||||||
return _audioPlayer!.state == ap.PlayerState.completed;
|
return _audioPlayer!.state == ap.PlayerState.completed;
|
||||||
} else {
|
} else {
|
||||||
throw UnimplementedError();
|
return !isPlaying && hasSource && await position == await duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +219,7 @@ 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 {
|
||||||
throw UnimplementedError();
|
return _assetsAudioPlayer!.isBuffering.valueOrNull ?? false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,7 +231,11 @@ class SpotubeAudioPlayer {
|
|||||||
return ap.DeviceFileSource(url);
|
return ap.DeviceFileSource(url);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw UnimplementedError();
|
if (url.startsWith("https")) {
|
||||||
|
return aap.Audio.network(url);
|
||||||
|
} else {
|
||||||
|
return aap.Audio.file(url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +245,7 @@ class SpotubeAudioPlayer {
|
|||||||
// audioplayers doesn't have the capability to preload
|
// audioplayers doesn't have the capability to preload
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
throw UnimplementedError();
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,35 +254,54 @@ class SpotubeAudioPlayer {
|
|||||||
if (apSupportedPlatform && urlType is ap.Source) {
|
if (apSupportedPlatform && urlType is ap.Source) {
|
||||||
await _audioPlayer?.play(urlType);
|
await _audioPlayer?.play(urlType);
|
||||||
} else {
|
} else {
|
||||||
throw UnimplementedError();
|
await _assetsAudioPlayer?.stop();
|
||||||
|
await _assetsAudioPlayer?.open(
|
||||||
|
urlType as aap.Playable,
|
||||||
|
autoStart: true,
|
||||||
|
audioFocusStrategy: const aap.AudioFocusStrategy.request(
|
||||||
|
resumeAfterInterruption: true,
|
||||||
|
),
|
||||||
|
loopMode: aap.LoopMode.none,
|
||||||
|
playInBackground: aap.PlayInBackground.enabled,
|
||||||
|
headPhoneStrategy: aap.HeadPhoneStrategy.pauseOnUnplugPlayOnPlug,
|
||||||
|
showNotification: false,
|
||||||
|
respectSilentMode: true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> pause() async {
|
Future<void> pause() async {
|
||||||
await _audioPlayer?.pause();
|
await _audioPlayer?.pause();
|
||||||
|
await _assetsAudioPlayer?.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> resume() async {
|
Future<void> resume() async {
|
||||||
await _audioPlayer?.resume();
|
await _audioPlayer?.resume();
|
||||||
|
await _assetsAudioPlayer?.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> stop() async {
|
Future<void> stop() async {
|
||||||
await _audioPlayer?.stop();
|
await _audioPlayer?.stop();
|
||||||
|
await _assetsAudioPlayer?.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setVolume(double volume) async {
|
Future<void> setVolume(double volume) async {
|
||||||
await _audioPlayer?.setVolume(volume);
|
await _audioPlayer?.setVolume(volume);
|
||||||
|
await _assetsAudioPlayer?.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
await _audioPlayer?.dispose();
|
await _audioPlayer?.dispose();
|
||||||
|
await _assetsAudioPlayer?.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
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
|
||||||
@ -24,6 +26,8 @@ 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"))
|
||||||
|
16
pubspec.lock
16
pubspec.lock
@ -97,6 +97,22 @@ 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:
|
||||||
|
name: assets_audio_player
|
||||||
|
sha256: dcea8cd9c11cd9c34586f2446bfcdf099362159c56f97517ba941ac151974ea9
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
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:
|
||||||
|
@ -10,6 +10,7 @@ environment:
|
|||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
args: ^2.3.2
|
args: ^2.3.2
|
||||||
|
assets_audio_player: ^3.0.6
|
||||||
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
|
||||||
|
Loading…
Reference in New Issue
Block a user