From da73bdde01ef99123bb03674b863b152833e6d50 Mon Sep 17 00:00:00 2001 From: MrGhostXUz Date: Mon, 27 May 2024 05:50:55 +0500 Subject: [PATCH 1/4] added refresh token mechanism --- just_audio/lib/just_audio.dart | 52 ++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/just_audio/lib/just_audio.dart b/just_audio/lib/just_audio.dart index 591fcf781..589f6768f 100644 --- a/just_audio/lib/just_audio.dart +++ b/just_audio/lib/just_audio.dart @@ -2077,6 +2077,8 @@ class _ProxyHttpServer { Uri addUriAudioSource(UriAudioSource source) { final uri = source.uri; final headers = {}; + final refreshCredentials = source.refreshCredentials; + final getAuthHeaders = source.getAuthHeaders; if (source.headers != null) { headers.addAll(source.headers!.cast()); } @@ -2085,6 +2087,8 @@ class _ProxyHttpServer { uri, headers: headers, userAgent: source._player?._userAgent, + refreshCredentials: refreshCredentials, + getAuthHeaders: getAuthHeaders, ); return uri.replace( scheme: 'http', @@ -2200,6 +2204,14 @@ abstract class AudioSource { final String _id; AudioPlayer? _player; + /// If a request needs authorization headers, logically it needs to refresh credentials. + /// In case of giving a playlist with lazy load, the player freezes if there is no + /// inbuilt refresh tokens mechanism + final Future Function()? refreshCredentials; + + /// getter function for additional headers to auth + final Map Function()? getAuthHeaders; + /// Creates an [AudioSource] from a [Uri] with optional headers by /// attempting to guess the type of stream. On iOS, this uses Apple's SDK to /// automatically detect the stream type. On Android, the type of stream will @@ -2223,7 +2235,7 @@ abstract class AudioSource { /// for background audio purposes, consider using the plugin audio_service /// instead of just_audio_background. static UriAudioSource uri(Uri uri, - {Map? headers, dynamic tag}) { + {Map? headers, dynamic tag, Future Function()? refreshCredentials, Map Function()? getAuthHeaders}) { bool hasExtension(Uri uri, String extension) => uri.path.toLowerCase().endsWith('.$extension') || uri.fragment.toLowerCase().endsWith('.$extension'); @@ -2232,7 +2244,7 @@ abstract class AudioSource { } else if (hasExtension(uri, 'm3u8')) { return HlsAudioSource(uri, headers: headers, tag: tag); } else { - return ProgressiveAudioSource(uri, headers: headers, tag: tag); + return ProgressiveAudioSource(uri, headers: headers, tag: tag, refreshCredentials: refreshCredentials, getAuthHeaders: getAuthHeaders,); } } @@ -2264,7 +2276,7 @@ abstract class AudioSource { return AudioSource.uri(Uri.parse('asset:///$keyName'), tag: tag); } - AudioSource() : _id = _uuid.v4(); + AudioSource({this.refreshCredentials, this.getAuthHeaders}) : _id = _uuid.v4(); @mustCallSuper Future _setup(AudioPlayer player) async { @@ -2303,7 +2315,7 @@ abstract class IndexedAudioSource extends AudioSource { final dynamic tag; Duration? duration; - IndexedAudioSource({this.tag, this.duration}); + IndexedAudioSource({this.tag, this.duration, super.refreshCredentials, super.getAuthHeaders}); @override void _shuffle({int? initialIndex}) {} @@ -2321,7 +2333,7 @@ abstract class UriAudioSource extends IndexedAudioSource { final Map? headers; Uri? _overrideUri; - UriAudioSource(this.uri, {this.headers, dynamic tag, Duration? duration}) + UriAudioSource(this.uri, {this.headers, dynamic tag, Duration? duration, super.refreshCredentials, super.getAuthHeaders,}) : super(tag: tag, duration: duration); /// If [uri] points to an asset, this gives us [_overrideUri] which is the URI @@ -2348,6 +2360,7 @@ abstract class UriAudioSource extends IndexedAudioSource { player._useProxyForRequestHeaders && (headers != null || player._userAgent != null)) { await player._proxy.ensureRunning(); + print('usage1'); _overrideUri = player._proxy.addUriAudioSource(this); } } @@ -2422,6 +2435,8 @@ class ProgressiveAudioSource extends UriAudioSource { super.tag, super.duration, this.options, + super.refreshCredentials, + super.getAuthHeaders, }); @override @@ -2531,6 +2546,8 @@ class ConcatenatingAudioSource extends AudioSource { required this.children, this.useLazyPreparation = true, ShuffleOrder? shuffleOrder, + super.refreshCredentials, + super.getAuthHeaders, }) : _shuffleOrder = shuffleOrder ?? DefaultShuffleOrder() ..insert(0, children.length); @@ -3321,6 +3338,8 @@ _ProxyHandler _proxyHandlerForUri( Uri uri, { Map? headers, String? userAgent, + Future Function()? refreshCredentials, + Map Function()? getAuthHeaders }) { // Keep redirected [Uri] to speed-up requests Uri? redirectedUri; @@ -3334,14 +3353,31 @@ _ProxyHandler _proxyHandlerForUri( .forEach((name, value) => requestHeaders[name] = value.join(', ')); // write supplied headers last (to ensure supplied headers aren't overwritten) headers?.forEach((name, value) => requestHeaders[name] = value); - final originRequest = + if(getAuthHeaders!=null) { + final authHeaders = getAuthHeaders(); + authHeaders.forEach((name, value) => requestHeaders[name] = value); + } + var originRequest = await _getUrl(client, redirectedUri ?? uri, headers: requestHeaders); host = originRequest.headers.value(HttpHeaders.hostHeader); - final originResponse = await originRequest.close(); + var originResponse = await originRequest.close(); if (originResponse.redirects.isNotEmpty) { redirectedUri = originResponse.redirects.last.location; } - + if(refreshCredentials!=null && originResponse.statusCode == HttpStatus.unauthorized) { + await refreshCredentials(); + if(getAuthHeaders!=null) { + final authHeaders = getAuthHeaders(); + authHeaders.forEach((name, value) => requestHeaders[name] = value); + } + originRequest = + await _getUrl(client, redirectedUri ?? uri, headers: requestHeaders); + host = originRequest.headers.value(HttpHeaders.hostHeader); + originResponse = await originRequest.close(); + if (originResponse.redirects.isNotEmpty) { + redirectedUri = originResponse.redirects.last.location; + } + } request.response.headers.clear(); originResponse.headers.forEach((name, value) { final filteredValue = value From 2cdd0b438f78507625ce0d08c7e445432e08ef88 Mon Sep 17 00:00:00 2001 From: "user.name" Date: Mon, 27 May 2024 11:36:04 +0500 Subject: [PATCH 2/4] remove unnecessary print --- just_audio/lib/just_audio.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/just_audio/lib/just_audio.dart b/just_audio/lib/just_audio.dart index 589f6768f..c97aa0a1a 100644 --- a/just_audio/lib/just_audio.dart +++ b/just_audio/lib/just_audio.dart @@ -2360,7 +2360,6 @@ abstract class UriAudioSource extends IndexedAudioSource { player._useProxyForRequestHeaders && (headers != null || player._userAgent != null)) { await player._proxy.ensureRunning(); - print('usage1'); _overrideUri = player._proxy.addUriAudioSource(this); } } From 549d65bdb4b766cbe94268fe1b78775fe1eb2d20 Mon Sep 17 00:00:00 2001 From: "user.name" Date: Fri, 19 Jul 2024 09:06:36 +0500 Subject: [PATCH 3/4] added logs --- just_audio/lib/just_audio.dart | 115 +++++++++++++++++++++++---------- 1 file changed, 82 insertions(+), 33 deletions(-) diff --git a/just_audio/lib/just_audio.dart b/just_audio/lib/just_audio.dart index c97aa0a1a..b6e275c74 100644 --- a/just_audio/lib/just_audio.dart +++ b/just_audio/lib/just_audio.dart @@ -244,11 +244,15 @@ class AudioPlayer { _currentIndexSubject.addStream(playbackEventStream .map((event) => event.currentIndex) .distinct() - .handleError((Object err, StackTrace stackTrace) {/* noop */})); + .handleError((Object err, StackTrace stackTrace) { + /* noop */ + })); _androidAudioSessionIdSubject.addStream(playbackEventStream .map((event) => event.androidAudioSessionId) .distinct() - .handleError((Object err, StackTrace stackTrace) {/* noop */})); + .handleError((Object err, StackTrace stackTrace) { + /* noop */ + })); _sequenceStateSubject.addStream(Rx.combineLatest5?, List?, int?, bool, LoopMode, SequenceState?>( sequenceStream, @@ -269,14 +273,18 @@ class AudioPlayer { loopMode, ); }, - ).distinct().handleError((Object err, StackTrace stackTrace) {/* noop */})); + ).distinct().handleError((Object err, StackTrace stackTrace) { + /* noop */ + })); _playerStateSubject.addStream( Rx.combineLatest2( playingStream, playbackEventStream, (playing, event) => PlayerState(playing, event.processingState)) .distinct() - .handleError((Object err, StackTrace stackTrace) {/* noop */})); + .handleError((Object err, StackTrace stackTrace) { + /* noop */ + })); _shuffleModeEnabledSubject.add(false); _loopModeSubject.add(LoopMode.off); _setPlatformActive(false, force: true) @@ -2078,6 +2086,7 @@ class _ProxyHttpServer { final uri = source.uri; final headers = {}; final refreshCredentials = source.refreshCredentials; + final onError = source.onError; final getAuthHeaders = source.getAuthHeaders; if (source.headers != null) { headers.addAll(source.headers!.cast()); @@ -2088,6 +2097,7 @@ class _ProxyHttpServer { headers: headers, userAgent: source._player?._userAgent, refreshCredentials: refreshCredentials, + onError: onError, getAuthHeaders: getAuthHeaders, ); return uri.replace( @@ -2209,6 +2219,8 @@ abstract class AudioSource { /// inbuilt refresh tokens mechanism final Future Function()? refreshCredentials; + final void Function(String message)? onError; + /// getter function for additional headers to auth final Map Function()? getAuthHeaders; @@ -2234,8 +2246,14 @@ abstract class AudioSource { /// provided by that package. If you wish to have more control over the tag /// for background audio purposes, consider using the plugin audio_service /// instead of just_audio_background. - static UriAudioSource uri(Uri uri, - {Map? headers, dynamic tag, Future Function()? refreshCredentials, Map Function()? getAuthHeaders}) { + static UriAudioSource uri( + Uri uri, { + Map? headers, + dynamic tag, + Future Function()? refreshCredentials, + void Function(String message)? onError, + Map Function()? getAuthHeaders, + }) { bool hasExtension(Uri uri, String extension) => uri.path.toLowerCase().endsWith('.$extension') || uri.fragment.toLowerCase().endsWith('.$extension'); @@ -2244,7 +2262,14 @@ abstract class AudioSource { } else if (hasExtension(uri, 'm3u8')) { return HlsAudioSource(uri, headers: headers, tag: tag); } else { - return ProgressiveAudioSource(uri, headers: headers, tag: tag, refreshCredentials: refreshCredentials, getAuthHeaders: getAuthHeaders,); + return ProgressiveAudioSource( + uri, + headers: headers, + tag: tag, + refreshCredentials: refreshCredentials, + onError: onError, + getAuthHeaders: getAuthHeaders, + ); } } @@ -2276,7 +2301,8 @@ abstract class AudioSource { return AudioSource.uri(Uri.parse('asset:///$keyName'), tag: tag); } - AudioSource({this.refreshCredentials, this.getAuthHeaders}) : _id = _uuid.v4(); + AudioSource({this.refreshCredentials, this.onError, this.getAuthHeaders}) + : _id = _uuid.v4(); @mustCallSuper Future _setup(AudioPlayer player) async { @@ -2315,7 +2341,12 @@ abstract class IndexedAudioSource extends AudioSource { final dynamic tag; Duration? duration; - IndexedAudioSource({this.tag, this.duration, super.refreshCredentials, super.getAuthHeaders}); + IndexedAudioSource( + {this.tag, + this.duration, + super.refreshCredentials, + super.onError, + super.getAuthHeaders}); @override void _shuffle({int? initialIndex}) {} @@ -2333,8 +2364,15 @@ abstract class UriAudioSource extends IndexedAudioSource { final Map? headers; Uri? _overrideUri; - UriAudioSource(this.uri, {this.headers, dynamic tag, Duration? duration, super.refreshCredentials, super.getAuthHeaders,}) - : super(tag: tag, duration: duration); + UriAudioSource( + this.uri, { + this.headers, + dynamic tag, + Duration? duration, + super.refreshCredentials, + super.onError, + super.getAuthHeaders, + }) : super(tag: tag, duration: duration); /// If [uri] points to an asset, this gives us [_overrideUri] which is the URI /// of the copied asset on the filesystem, otherwise it gives us the original @@ -2435,6 +2473,7 @@ class ProgressiveAudioSource extends UriAudioSource { super.duration, this.options, super.refreshCredentials, + super.onError, super.getAuthHeaders, }); @@ -3224,6 +3263,7 @@ class _InProgressCacheResponse { // ignore: close_sinks final controller = ReplaySubject>(); final int? end; + _InProgressCacheResponse({ required this.end, }); @@ -3333,13 +3373,12 @@ _ProxyHandler _proxyHandlerForSource(StreamAudioSource source) { } /// A proxy handler for serving audio from a URI with optional headers. -_ProxyHandler _proxyHandlerForUri( - Uri uri, { - Map? headers, - String? userAgent, - Future Function()? refreshCredentials, - Map Function()? getAuthHeaders -}) { +_ProxyHandler _proxyHandlerForUri(Uri uri, + {Map? headers, + String? userAgent, + Future Function()? refreshCredentials, + void Function(String message)? onError, + Map Function()? getAuthHeaders,}) { // Keep redirected [Uri] to speed-up requests Uri? redirectedUri; Future handler(_ProxyHttpServer server, HttpRequest request) async { @@ -3352,7 +3391,7 @@ _ProxyHandler _proxyHandlerForUri( .forEach((name, value) => requestHeaders[name] = value.join(', ')); // write supplied headers last (to ensure supplied headers aren't overwritten) headers?.forEach((name, value) => requestHeaders[name] = value); - if(getAuthHeaders!=null) { + if (getAuthHeaders != null) { final authHeaders = getAuthHeaders(); authHeaders.forEach((name, value) => requestHeaders[name] = value); } @@ -3363,19 +3402,20 @@ _ProxyHandler _proxyHandlerForUri( if (originResponse.redirects.isNotEmpty) { redirectedUri = originResponse.redirects.last.location; } - if(refreshCredentials!=null && originResponse.statusCode == HttpStatus.unauthorized) { - await refreshCredentials(); - if(getAuthHeaders!=null) { - final authHeaders = getAuthHeaders(); - authHeaders.forEach((name, value) => requestHeaders[name] = value); - } - originRequest = - await _getUrl(client, redirectedUri ?? uri, headers: requestHeaders); - host = originRequest.headers.value(HttpHeaders.hostHeader); - originResponse = await originRequest.close(); - if (originResponse.redirects.isNotEmpty) { - redirectedUri = originResponse.redirects.last.location; - } + if (refreshCredentials != null && + originResponse.statusCode == HttpStatus.unauthorized) { + await refreshCredentials(); + if (getAuthHeaders != null) { + final authHeaders = getAuthHeaders(); + authHeaders.forEach((name, value) => requestHeaders[name] = value); + } + originRequest = await _getUrl(client, redirectedUri ?? uri, + headers: requestHeaders); + host = originRequest.headers.value(HttpHeaders.hostHeader); + originResponse = await originRequest.close(); + if (originResponse.redirects.isNotEmpty) { + redirectedUri = originResponse.redirects.last.location; + } } request.response.headers.clear(); originResponse.headers.forEach((name, value) { @@ -3430,7 +3470,10 @@ _ProxyHandler _proxyHandlerForUri( } await request.response.flush(); await request.response.close(); - } on HttpException { + } on HttpException catch (e) { + if (onError != null) { + onError('on HttpException: ${e.toString()}'); + } // We likely are dealing with a streaming protocol if (uri.scheme == 'http') { // Try parsing HTTP 0.9 response @@ -3469,6 +3512,10 @@ _ProxyHandler _proxyHandlerForUri( await socket.flush(); await done.future; } + } catch (e) { + if (onError != null) { + onError(e.toString()); + } } } @@ -3994,7 +4041,9 @@ class AndroidEqualizer extends AudioEffect with AndroidAudioEffect { } bool _isAndroid() => !kIsWeb && Platform.isAndroid; + bool _isDarwin() => !kIsWeb && (Platform.isIOS || Platform.isMacOS); + bool _isUnitTest() => !kIsWeb && Platform.environment['FLUTTER_TEST'] == 'true'; /// Backwards compatible extensions on rxdart's ValueStream From a61e9d025c678838c786f0eb0a4a9df07b649943 Mon Sep 17 00:00:00 2001 From: "user.name" Date: Mon, 22 Jul 2024 11:14:36 +0500 Subject: [PATCH 4/4] added some functionality --- just_audio/lib/just_audio.dart | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/just_audio/lib/just_audio.dart b/just_audio/lib/just_audio.dart index b6e275c74..3f43d8379 100644 --- a/just_audio/lib/just_audio.dart +++ b/just_audio/lib/just_audio.dart @@ -811,7 +811,7 @@ class AudioPlayer { final initialSeekValues = _initialSeekValues; _initialSeekValues = null; return await _load(await _platform, _audioSource!, - initialSeekValues: initialSeekValues); + initialSeekValues: initialSeekValues, onError: _audioSource!.onError); } else { // This will implicitly load the current audio source. return await _setPlatformActive(true); @@ -845,7 +845,7 @@ class AudioPlayer { } Future _load(AudioPlayerPlatform platform, AudioSource source, - {_InitialSeekValues? initialSeekValues}) async { + {_InitialSeekValues? initialSeekValues, void Function(String error)? onError}) async { final activationNumber = _activationCount; void checkInterruption() { if (_activationCount != activationNumber) { @@ -871,6 +871,7 @@ class AudioPlayer { _durationSubject.add(duration); if (platform != _platformValue) { // the platform has changed since we started loading, so abort. + if (onError!=null) onError("PlatformException(code: 'abort', message: 'Loading interrupted')"); throw PlatformException(code: 'abort', message: 'Loading interrupted'); } // Wait for loading state to pass. @@ -880,9 +881,11 @@ class AudioPlayer { return duration; } on PlatformException catch (e) { try { + if (onError!=null) onError("PlayerException: ${int.parse(e.code)} ${e.message}"); throw PlayerException(int.parse(e.code), e.message, (e.details as Map?)?.cast()); - } on FormatException catch (_) { + } on FormatException catch (f) { + if (onError!=null) onError("FormatException: $f"); if (e.code == 'abort') { throw PlayerInterruptedException(e.message); } else { @@ -910,7 +913,7 @@ class AudioPlayer { start: start, end: end, tag: tag, - )); + ), onError: _audioSource?.onError); return duration; } @@ -1507,7 +1510,7 @@ class AudioPlayer { _InitialSeekValues(position: position, index: currentIndex); _initialSeekValues = null; final duration = await _load(platform, _audioSource!, - initialSeekValues: initialSeekValues); + initialSeekValues: initialSeekValues, onError: _audioSource!.onError); if (checkInterruption()) return platform; durationCompleter.complete(duration); } catch (e, stackTrace) {