import 'dart:io'; import 'package:media_kit/media_kit.dart' hide Track; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:flutter/foundation.dart'; import 'package:spotube/services/audio_player/custom_player.dart'; import 'dart:async'; import 'package:media_kit/media_kit.dart' as mk; import 'package:spotube/services/audio_player/playback_state.dart'; import 'package:spotube/utils/platform.dart'; part 'audio_players_streams_mixin.dart'; part 'audio_player_impl.dart'; class SpotubeMedia extends mk.Media { static int serverPort = 0; static String get _host => kIsWindows ? "localhost" : InternetAddress.anyIPv4.address; static String _queries(SpotubeFullTrackObject track) { final params = TrackSourceQuery.fromTrack(track).toJson(); return params.entries .map((e) => "${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value is List ? e.value.join(",") : e.value.toString())}") .join("&"); } final SpotubeTrackObject track; SpotubeMedia( this.track, { Map? extras, super.httpHeaders, }) : assert( track is SpotubeLocalTrackObject || track is SpotubeFullTrackObject, "Track must be a either a local track or a full track object with ISRC", ), // If the track is a local track, use its path, otherwise use the server URL super( track is SpotubeLocalTrackObject ? track.path : "http://$_host:$serverPort/stream/${track.id}?${_queries(track as SpotubeFullTrackObject)}", ); } abstract class AudioPlayerInterface { final CustomPlayer _mkPlayer; AudioPlayerInterface() : _mkPlayer = CustomPlayer( configuration: const mk.PlayerConfiguration( title: "Spotube", logLevel: kDebugMode ? mk.MPVLogLevel.info : mk.MPVLogLevel.error, ), ) { _mkPlayer.stream.error.listen((event) { AppLogger.reportError(event, StackTrace.current); }); } /// Whether the current platform supports the audioplayers plugin static const bool _mkSupportedPlatform = true; bool get mkSupportedPlatform => _mkSupportedPlatform; Duration get duration { return _mkPlayer.state.duration; } Playlist get playlist { return _mkPlayer.state.playlist; } Duration get position { return _mkPlayer.state.position; } Duration get bufferedPosition { return _mkPlayer.state.buffer; } Future get selectedDevice async { return _mkPlayer.state.audioDevice; } Future> get devices async { return _mkPlayer.state.audioDevices; } bool get hasSource { return _mkPlayer.state.playlist.medias.isNotEmpty; } // states bool get isPlaying { return _mkPlayer.state.playing; } bool get isPaused { return !_mkPlayer.state.playing; } bool get isStopped { return !hasSource; } Future get isCompleted async { return _mkPlayer.state.completed; } bool get isShuffled { return _mkPlayer.shuffled; } PlaylistMode get loopMode { return _mkPlayer.state.playlistMode; } /// Returns the current volume of the player, between 0 and 1 double get volume { return _mkPlayer.state.volume / 100; } bool get isBuffering { return _mkPlayer.state.buffering; } }