From 6079a481c48f66cec779053f40eeaf091cd0bdd7 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Mon, 27 Jun 2022 02:27:19 +1000 Subject: [PATCH 01/64] Implement lazy loading treadmill on iOS. --- just_audio/darwin/Classes/AudioPlayer.m | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/just_audio/darwin/Classes/AudioPlayer.m b/just_audio/darwin/Classes/AudioPlayer.m index ad611ccaf..084fe3418 100644 --- a/just_audio/darwin/Classes/AudioPlayer.m +++ b/just_audio/darwin/Classes/AudioPlayer.m @@ -11,6 +11,8 @@ #import #include +#define MAX_QUEUE_LENGTH 2 + // TODO: Check for and report invalid state transitions. // TODO: Apply Apple's guidance on seeking: https://developer.apple.com/library/archive/qa/qa1820/_index.html @implementation AudioPlayer { @@ -516,7 +518,7 @@ - (void)enqueueFrom:(int)index { // Regenerate queue if (!existingItem || _loopMode != loopOne) { BOOL include = NO; - for (int i = 0; i < [_order count]; i++) { + for (int i = 0; i < [_order count] && _player.items.count < MAX_QUEUE_LENGTH; i++) { int si = [_order[i] intValue]; if (si == _index) include = YES; if (include && _indexedAudioSources[si].playerItem != existingItem) { @@ -531,7 +533,7 @@ - (void)enqueueFrom:(int)index { } // Add next loop item if we're looping - if (_order.count > 0) { + if (_order.count > 0 && _player.items.count < MAX_QUEUE_LENGTH) { if (_loopMode == loopAll) { int si = [_order[0] intValue]; //NSLog(@"### add loop item:%d", si); @@ -933,15 +935,14 @@ - (void)observeValueForKeyPath:(NSString *)keyPath IndexedAudioSource *audioSource = playerItem.audioSource; if (_loopMode == loopOne) { [audioSource flip]; - [self enqueueFrom:_index]; } else if (_loopMode == loopAll) { if (_index == [_order[0] intValue] && playerItem == audioSource.playerItem2) { [audioSource flip]; - [self enqueueFrom:_index]; } else { [self updateEndAction]; } } + [self enqueueFrom:_index]; _justAdvanced = NO; } } else if ([keyPath isEqualToString:@"loadedTimeRanges"]) { From 75d574fca84b1dae3b39dfa82f24381fe46f2004 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 18 Aug 2023 12:06:11 -0700 Subject: [PATCH 02/64] Stop passing a nullable value to Completer.completer. --- just_audio/lib/just_audio.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/just_audio/lib/just_audio.dart b/just_audio/lib/just_audio.dart index b35958241..61b275bfa 100644 --- a/just_audio/lib/just_audio.dart +++ b/just_audio/lib/just_audio.dart @@ -3756,7 +3756,6 @@ class AndroidEqualizerParameters { /// An [AudioEffect] for Android that can adjust the gain for different /// frequency bands of an [AudioPlayer]'s audio signal. class AndroidEqualizer extends AudioEffect with AndroidAudioEffect { - AndroidEqualizerParameters? _parameters; final Completer _parametersCompleter = Completer(); @@ -3772,9 +3771,9 @@ class AndroidEqualizer extends AudioEffect with AndroidAudioEffect { } final response = await platform .androidEqualizerGetParameters(AndroidEqualizerGetParametersRequest()); - _parameters = + final parameters = AndroidEqualizerParameters._fromMessage(_player!, response.parameters); - _parametersCompleter.complete(_parameters); + _parametersCompleter.complete(parameters); } /// The parameter values of this equalizer. From 08a83c1480cba4763c9b82562965ea2f3a3fe6ef Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Sun, 20 Aug 2023 20:41:36 -0700 Subject: [PATCH 03/64] Address feedback --- just_audio/lib/just_audio.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/just_audio/lib/just_audio.dart b/just_audio/lib/just_audio.dart index 61b275bfa..60cb5e67b 100644 --- a/just_audio/lib/just_audio.dart +++ b/just_audio/lib/just_audio.dart @@ -3771,9 +3771,9 @@ class AndroidEqualizer extends AudioEffect with AndroidAudioEffect { } final response = await platform .androidEqualizerGetParameters(AndroidEqualizerGetParametersRequest()); - final parameters = + final receivedParameters = AndroidEqualizerParameters._fromMessage(_player!, response.parameters); - _parametersCompleter.complete(parameters); + _parametersCompleter.complete(receivedParameters); } /// The parameter values of this equalizer. From c341b6c9e41c009ff1199bd2d3785f357310bd11 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Tue, 22 Aug 2023 00:27:06 +1000 Subject: [PATCH 04/64] Bump version to 0.9.35 --- just_audio/CHANGELOG.md | 4 ++++ just_audio/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/just_audio/CHANGELOG.md b/just_audio/CHANGELOG.md index 735220e18..876bb6a49 100644 --- a/just_audio/CHANGELOG.md +++ b/just_audio/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.35 + +* Fix nullable completer argument type (@srawlins). + ## 0.9.34 * Support AGP 8 (@josephcrowell). diff --git a/just_audio/pubspec.yaml b/just_audio/pubspec.yaml index bbab38ff3..e25428343 100644 --- a/just_audio/pubspec.yaml +++ b/just_audio/pubspec.yaml @@ -1,6 +1,6 @@ name: just_audio description: A feature-rich audio player for Flutter. Loop, clip and concatenate any sound from any source (asset/file/URL/stream) in a variety of audio formats with gapless playback. -version: 0.9.34 +version: 0.9.35 repository: https://github.com/ryanheise/just_audio/tree/minor/just_audio issue_tracker: https://github.com/ryanheise/just_audio/issues topics: From a799d77af722962629473e6de4c3afd2ee51aba1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 8 Sep 2023 12:01:03 +0800 Subject: [PATCH 05/64] Upgrade UUID dependency from ^3.0.1 to ^4.0.0 --- just_audio/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/just_audio/pubspec.yaml b/just_audio/pubspec.yaml index e25428343..25980ad4a 100644 --- a/just_audio/pubspec.yaml +++ b/just_audio/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: path: ^1.8.0 path_provider: ^2.0.0 async: ^2.5.0 - uuid: ^3.0.1 + uuid: ^4.0.0 crypto: ^3.0.0 meta: ^1.3.0 flutter: From 32c5b89ea796df3185bd09d011604d39942fcddd Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 8 Sep 2023 12:32:29 +0800 Subject: [PATCH 06/64] Fix version constraint --- just_audio/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/just_audio/pubspec.yaml b/just_audio/pubspec.yaml index 25980ad4a..53b32e2bf 100644 --- a/just_audio/pubspec.yaml +++ b/just_audio/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: path: ^1.8.0 path_provider: ^2.0.0 async: ^2.5.0 - uuid: ^4.0.0 + uuid: '>=3.0.1 <5.0.0' crypto: ^3.0.0 meta: ^1.3.0 flutter: From 4e8b3823d557f85b70a24e128e8ed18ed6445102 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Tue, 12 Sep 2023 16:50:29 +1000 Subject: [PATCH 07/64] Add uuid update to CHANGELOG --- just_audio/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/just_audio/CHANGELOG.md b/just_audio/CHANGELOG.md index 876bb6a49..e1b548570 100644 --- a/just_audio/CHANGELOG.md +++ b/just_audio/CHANGELOG.md @@ -1,6 +1,7 @@ ## 0.9.35 * Fix nullable completer argument type (@srawlins). +* Support uuid 4.0.0 (@Pante). ## 0.9.34 From b1f34f0c36f6ac06e09f1d2918e2108964064dea Mon Sep 17 00:00:00 2001 From: Abdellatif Bakka Date: Sun, 24 Sep 2023 18:41:22 +0100 Subject: [PATCH 08/64] update --- just_audio/lib/just_audio.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/just_audio/lib/just_audio.dart b/just_audio/lib/just_audio.dart index 60cb5e67b..cd210f951 100644 --- a/just_audio/lib/just_audio.dart +++ b/just_audio/lib/just_audio.dart @@ -864,7 +864,7 @@ class AudioPlayer { /// original [AudioSource]. If [end] is null, it will be reset to the end of /// the original [AudioSource]. This method cannot be called from the /// [ProcessingState.idle] state. - Future setClip({Duration? start, Duration? end}) async { + Future setClip({Duration? start, Duration? end, dynamic tag: tag}) async { if (_disposed) return null; _setPlatformActive(true)?.catchError((dynamic e) async => null); final duration = await _load( @@ -875,6 +875,7 @@ class AudioPlayer { child: _audioSource as UriAudioSource, start: start, end: end, + tag: tag )); return duration; } From 95457ac656c6d4153cb69865220200048ce2d840 Mon Sep 17 00:00:00 2001 From: Abdellatif Bakka Date: Sun, 24 Sep 2023 19:03:31 +0100 Subject: [PATCH 09/64] update --- just_audio/lib/just_audio.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/just_audio/lib/just_audio.dart b/just_audio/lib/just_audio.dart index cd210f951..89112c3be 100644 --- a/just_audio/lib/just_audio.dart +++ b/just_audio/lib/just_audio.dart @@ -864,7 +864,7 @@ class AudioPlayer { /// original [AudioSource]. If [end] is null, it will be reset to the end of /// the original [AudioSource]. This method cannot be called from the /// [ProcessingState.idle] state. - Future setClip({Duration? start, Duration? end, dynamic tag: tag}) async { + Future setClip({Duration? start, Duration? end, dynamic tag}) async { if (_disposed) return null; _setPlatformActive(true)?.catchError((dynamic e) async => null); final duration = await _load( From f4f85d7b5ba81ac91fd8bff39ecd2a0a2b8b752a Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Mon, 9 Oct 2023 13:15:07 +1100 Subject: [PATCH 10/64] Add setAllowsExternalPlayback on iOS/macOS. --- just_audio/CHANGELOG.md | 4 +++ just_audio/darwin/Classes/AudioPlayer.m | 17 ++++++++++++ .../ios/Runner.xcodeproj/project.pbxproj | 3 ++- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- just_audio/lib/just_audio.dart | 15 +++++++++++ just_audio/pubspec.yaml | 4 +-- just_audio_platform_interface/CHANGELOG.md | 4 +++ .../lib/just_audio_platform_interface.dart | 27 +++++++++++++++++++ .../lib/method_channel_just_audio.dart | 8 ++++++ just_audio_platform_interface/pubspec.yaml | 2 +- just_audio_web/pubspec.yaml | 2 +- 11 files changed, 82 insertions(+), 6 deletions(-) diff --git a/just_audio/CHANGELOG.md b/just_audio/CHANGELOG.md index e1b548570..c62bbf25a 100644 --- a/just_audio/CHANGELOG.md +++ b/just_audio/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.36 + +* Add setAllowsExternalPlayback on iOS/macOS. + ## 0.9.35 * Fix nullable completer argument type (@srawlins). diff --git a/just_audio/darwin/Classes/AudioPlayer.m b/just_audio/darwin/Classes/AudioPlayer.m index ad611ccaf..fbc70cdf4 100644 --- a/just_audio/darwin/Classes/AudioPlayer.m +++ b/just_audio/darwin/Classes/AudioPlayer.m @@ -39,6 +39,7 @@ @implementation AudioPlayer { FlutterResult _playResult; id _timeObserver; BOOL _automaticallyWaitsToMinimizeStalling; + BOOL _allowsExternalPlayback; LoadControl *_loadControl; BOOL _playing; float _speed; @@ -81,6 +82,7 @@ - (instancetype)initWithRegistrar:(NSObject *)registrar _loadResult = nil; _playResult = nil; _automaticallyWaitsToMinimizeStalling = YES; + _allowsExternalPlayback = NO; _loadControl = nil; if (loadConfiguration != (id)[NSNull null]) { NSDictionary *map = loadConfiguration[@"darwinLoadControl"]; @@ -147,6 +149,9 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } else if ([@"setPreferredPeakBitRate" isEqualToString:call.method]) { [self setPreferredPeakBitRate:(NSNumber *)request[@"bitRate"]]; result(@{}); + } else if ([@"setAllowsExternalPlayback" isEqualToString:call.method]) { + [self setAllowsExternalPlayback:(BOOL)([request[@"allowsExternalPlayback"] intValue] == 1)]; + result(@{}); } else if ([@"seek" isEqualToString:call.method]) { CMTime position = request[@"position"] == (id)[NSNull null] ? kCMTimePositiveInfinity : CMTimeMake([request[@"position"] longLongValue], 1000000); [self seek:position index:request[@"index"] completionHandler:^(BOOL finished) { @@ -642,6 +647,9 @@ - (void)load:(NSDictionary *)source initialPosition:(CMTime)initialPosition init options:NSKeyValueObservingOptionNew context:nil]; } + if (@available(macOS 10.11, iOS 6.0, *)) { + _player.allowsExternalPlayback = _allowsExternalPlayback; + } [_player addObserver:self forKeyPath:@"currentItem" options:NSKeyValueObservingOptionNew @@ -1157,6 +1165,15 @@ - (void)setPreferredPeakBitRate:(NSNumber *)preferredPeakBitRate { } } +- (void)setAllowsExternalPlayback:(BOOL)allowsExternalPlayback { + _allowsExternalPlayback = allowsExternalPlayback; + if (@available(macOS 10.11, iOS 6.0, *)) { + if (_player) { + _player.allowsExternalPlayback = allowsExternalPlayback; + } + } +} + - (void)seek:(CMTime)position index:(NSNumber *)newIndex completionHandler:(void (^)(BOOL))completionHandler { if (_processingState == none || _processingState == loading) { if (completionHandler) { diff --git a/just_audio/example/ios/Runner.xcodeproj/project.pbxproj b/just_audio/example/ios/Runner.xcodeproj/project.pbxproj index bc869cc2b..6ff5b997a 100644 --- a/just_audio/example/ios/Runner.xcodeproj/project.pbxproj +++ b/just_audio/example/ios/Runner.xcodeproj/project.pbxproj @@ -166,7 +166,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -215,6 +215,7 @@ files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( diff --git a/just_audio/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/just_audio/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3db53b6e1..b52b2e698 100644 --- a/just_audio/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/just_audio/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ _preferredPeakBitRate; + /// Whether the player allows external playback on iOS/macOS, defaults to + /// false. + bool get allowsExternalPlayback => _allowsExternalPlayback; + /// The current position of the player. Duration get position => _getPositionFor(_playbackEvent); @@ -1100,6 +1105,16 @@ class AudioPlayer { SetPreferredPeakBitRateRequest(bitRate: preferredPeakBitRate)); } + /// Sets allowsExternalPlayback on iOS/macOS, defaults to false. + Future setAllowsExternalPlayback( + final bool allowsExternalPlayback) async { + if (_disposed) return; + _allowsExternalPlayback = allowsExternalPlayback; + await (await _platform).setAllowsExternalPlayback( + SetAllowsExternalPlaybackRequest( + allowsExternalPlayback: allowsExternalPlayback)); + } + /// Seeks to a particular [position]. If a composition of multiple /// [AudioSource]s has been loaded, you may also specify [index] to seek to a /// particular item within that sequence. This method has no effect unless diff --git a/just_audio/pubspec.yaml b/just_audio/pubspec.yaml index 53b32e2bf..3465066a2 100644 --- a/just_audio/pubspec.yaml +++ b/just_audio/pubspec.yaml @@ -1,6 +1,6 @@ name: just_audio description: A feature-rich audio player for Flutter. Loop, clip and concatenate any sound from any source (asset/file/URL/stream) in a variety of audio formats with gapless playback. -version: 0.9.35 +version: 0.9.36 repository: https://github.com/ryanheise/just_audio/tree/minor/just_audio issue_tracker: https://github.com/ryanheise/just_audio/issues topics: @@ -14,7 +14,7 @@ environment: flutter: ">=3.0.0" dependencies: - just_audio_platform_interface: ^4.2.1 + just_audio_platform_interface: ^4.2.2 # just_audio_platform_interface: # path: ../just_audio_platform_interface just_audio_web: ^0.4.8 diff --git a/just_audio_platform_interface/CHANGELOG.md b/just_audio_platform_interface/CHANGELOG.md index 56fe4846b..d908dfe03 100644 --- a/just_audio_platform_interface/CHANGELOG.md +++ b/just_audio_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.2.2 + +* Add setAllowsExternalPlayback on iOS/macOS. + ## 4.2.1 * Update minimum flutter version to 3.0. diff --git a/just_audio_platform_interface/lib/just_audio_platform_interface.dart b/just_audio_platform_interface/lib/just_audio_platform_interface.dart index 03bf7223c..44da83ee5 100644 --- a/just_audio_platform_interface/lib/just_audio_platform_interface.dart +++ b/just_audio_platform_interface/lib/just_audio_platform_interface.dart @@ -154,6 +154,14 @@ abstract class AudioPlayerPlatform { "setPreferredPeakBitRate() has not been implemented."); } + /// On iOS and macOS, sets the allowsExternalPlayback option, and does nothing + /// on other platforms. + Future setAllowsExternalPlayback( + SetAllowsExternalPlaybackRequest request) { + throw UnimplementedError( + "setAllowsExternalPlayback() has not been implemented."); + } + /// Seeks to the given index and position. Future seek(SeekRequest request) { throw UnimplementedError("seek() has not been implemented."); @@ -700,6 +708,25 @@ class SetPreferredPeakBitRateResponse { SetPreferredPeakBitRateResponse(); } +/// Information communicated to the platform implementation when setting the +/// automaticallyWaitsToMinimizeStalling option. +class SetAllowsExternalPlaybackRequest { + final bool allowsExternalPlayback; + + SetAllowsExternalPlaybackRequest({required this.allowsExternalPlayback}); + + Map toMap() => { + 'allowsExternalPlayback': allowsExternalPlayback, + }; +} + +/// Information returned by the platform implementation after setting the +/// automaticallyWaitsToMinimizeStalling option. +class SetAllowsExternalPlaybackResponse { + static SetAllowsExternalPlaybackResponse fromMap(Map map) => + SetAllowsExternalPlaybackResponse(); +} + /// Information communicated to the platform implementation when seeking to a /// position and index. class SeekRequest { diff --git a/just_audio_platform_interface/lib/method_channel_just_audio.dart b/just_audio_platform_interface/lib/method_channel_just_audio.dart index 24daadd21..6a9010ac0 100644 --- a/just_audio_platform_interface/lib/method_channel_just_audio.dart +++ b/just_audio_platform_interface/lib/method_channel_just_audio.dart @@ -147,6 +147,14 @@ class MethodChannelAudioPlayer extends AudioPlayerPlatform { 'setPreferredPeakBitRate', request.toMap()))!); } + @override + Future setAllowsExternalPlayback( + SetAllowsExternalPlaybackRequest request) async { + return SetAllowsExternalPlaybackResponse.fromMap( + (await _channel.invokeMethod>( + 'setAllowsExternalPlayback', request.toMap()))!); + } + @override Future seek(SeekRequest request) async { return SeekResponse.fromMap((await _channel diff --git a/just_audio_platform_interface/pubspec.yaml b/just_audio_platform_interface/pubspec.yaml index e6dadfbf3..f11c159da 100644 --- a/just_audio_platform_interface/pubspec.yaml +++ b/just_audio_platform_interface/pubspec.yaml @@ -3,7 +3,7 @@ description: A common platform interface for the just_audio plugin. Different pl homepage: https://github.com/ryanheise/just_audio/tree/master/just_audio_platform_interface # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 4.2.1 +version: 4.2.2 dependencies: flutter: diff --git a/just_audio_web/pubspec.yaml b/just_audio_web/pubspec.yaml index cfc866a62..f7e176a5f 100644 --- a/just_audio_web/pubspec.yaml +++ b/just_audio_web/pubspec.yaml @@ -11,7 +11,7 @@ flutter: fileName: just_audio_web.dart dependencies: - just_audio_platform_interface: ^4.2.1 + just_audio_platform_interface: ^4.2.2 # just_audio_platform_interface: # path: ../just_audio_platform_interface flutter: From 63611f329ce794856bf562127dde73954efdb2ce Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Tue, 17 Oct 2023 01:59:11 +1100 Subject: [PATCH 11/64] Android extractor options, proxyless headers/user-agent. --- just_audio/CHANGELOG.md | 3 + just_audio/README.md | 8 +- .../com/ryanheise/just_audio/AudioPlayer.java | 34 ++++- .../just_audio/MainMethodCallHandler.java | 13 +- just_audio/example/pubspec.yaml | 2 +- just_audio/lib/just_audio.dart | 80 ++++++++++-- just_audio/pubspec.yaml | 2 +- just_audio/test/just_audio_test.dart | 2 +- just_audio_background/example/pubspec.yaml | 2 +- .../lib/just_audio_platform_interface.dart | 123 +++++++++++++----- just_audio_platform_interface/pubspec.yaml | 2 +- just_audio_web/pubspec.yaml | 2 +- 12 files changed, 213 insertions(+), 60 deletions(-) diff --git a/just_audio/CHANGELOG.md b/just_audio/CHANGELOG.md index c62bbf25a..30b25de53 100644 --- a/just_audio/CHANGELOG.md +++ b/just_audio/CHANGELOG.md @@ -1,6 +1,9 @@ ## 0.9.36 * Add setAllowsExternalPlayback on iOS/macOS. +* Support index-based seeking on Android. +* userAgent works without proxy on Android. +* headers work without proxy on Android. ## 0.9.35 diff --git a/just_audio/README.md b/just_audio/README.md index 6c1618f4c..5027b164e 100644 --- a/just_audio/README.md +++ b/just_audio/README.md @@ -105,8 +105,6 @@ final duration = await player.setUrl('https://foo.com/bar.mp3', headers: {'header1': 'value1', 'header2': 'value2'}); ``` -Note: headers are implemented via a local HTTP proxy which on Android, iOS and macOS requires non-HTTPS support to be enabled. See [Platform Specific Configuration](#platform-specific-configuration). - ### Working with caches ```dart @@ -242,7 +240,7 @@ If you wish to connect to non-HTTPS URLs (typically HTTP), also add the followin ``` -Note that just_audio's proxy (used to implement features such as headers, caching and stream audio sources) runs on a `localhost` HTTP server, and this also requires cleartext access to be enabled. You can either enable this via the option above which also enables access to any non-HTTPS URL, or you can instead limit cleartext access to just `localhost` URLs by defining a network security config. To use this approach, create the file `android/app/src/main/res/xml/network_security_config.xml`: +Note that just_audio's proxy (used to implement features such as caching and stream audio sources) runs on a `localhost` HTTP server, and this also requires cleartext access to be enabled. You can either enable this via the option above which also enables access to any non-HTTPS URL, or you can instead limit cleartext access to just `localhost` URLs by defining a network security config. To use this approach, create the file `android/app/src/main/res/xml/network_security_config.xml`: ```xml @@ -301,7 +299,7 @@ post_install do |installer| end ``` -If you wish to connect to non-HTTPS URLs, or if you use a feature that depends on the proxy such as headers, caching or stream audio sources, add the following to your `Info.plist` file: +If you wish to connect to non-HTTPS URLs, or if you use a feature that depends on the proxy such as caching or stream audio sources, add the following to your `Info.plist` file: ```xml NSAppTransportSecurity @@ -322,7 +320,7 @@ To allow your macOS application to access audio files on the Internet, add the f ``` -If you wish to connect to non-HTTPS URLs, or if you use a feature that depends on the proxy such as headers, caching or stream audio sources, add the following to your `Info.plist` file: +If you wish to connect to non-HTTPS URLs, or if you use a feature that depends on the proxy such as caching or stream audio sources, add the following to your `Info.plist` file: ```xml NSAppTransportSecurity diff --git a/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java b/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java index 37cb1d000..8dadebdb4 100644 --- a/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java +++ b/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java @@ -92,6 +92,7 @@ public class AudioPlayer implements MethodCallHandler, Player.Listener, Metadata private AudioAttributes pendingAudioAttributes; private LoadControl loadControl; private boolean offloadSchedulingEnabled; + private String userAgent; private LivePlaybackSpeedControl livePlaybackSpeedControl; private List rawAudioEffects; private List audioEffects = new ArrayList(); @@ -134,10 +135,18 @@ public void run() { } }; - public AudioPlayer(final Context applicationContext, final BinaryMessenger messenger, final String id, Map audioLoadConfiguration, List rawAudioEffects, Boolean offloadSchedulingEnabled) { + public AudioPlayer( + final Context applicationContext, + final BinaryMessenger messenger, + final String id, Map audioLoadConfiguration, + List rawAudioEffects, + Boolean offloadSchedulingEnabled, + String userAgent + ) { this.context = applicationContext; this.rawAudioEffects = rawAudioEffects; this.offloadSchedulingEnabled = offloadSchedulingEnabled != null ? offloadSchedulingEnabled : false; + this.userAgent = userAgent != null ? userAgent : Util.getUserAgent(context, "just_audio"); methodChannel = new MethodChannel(messenger, "com.ryanheise.just_audio.methods." + id); methodChannel.setMethodCallHandler(this); eventChannel = new BetterEventChannel(messenger, "com.ryanheise.just_audio.events." + id); @@ -593,20 +602,20 @@ private MediaSource decodeAudioSource(final Object json) { String id = (String)map.get("id"); switch ((String)map.get("type")) { case "progressive": - return new ProgressiveMediaSource.Factory(buildDataSourceFactory(), extractorsFactory) + return new ProgressiveMediaSource.Factory(buildDataSourceFactory(mapGet(map, "headers")), extractorsFactory) .createMediaSource(new MediaItem.Builder() .setUri(Uri.parse((String)map.get("uri"))) .setTag(id) .build()); case "dash": - return new DashMediaSource.Factory(buildDataSourceFactory()) + return new DashMediaSource.Factory(buildDataSourceFactory(mapGet(map, "headers"))) .createMediaSource(new MediaItem.Builder() .setUri(Uri.parse((String)map.get("uri"))) .setMimeType(MimeTypes.APPLICATION_MPD) .setTag(id) .build()); case "hls": - return new HlsMediaSource.Factory(buildDataSourceFactory()) + return new HlsMediaSource.Factory(buildDataSourceFactory(mapGet(map, "headers"))) .createMediaSource(new MediaItem.Builder() .setUri(Uri.parse((String)map.get("uri"))) .setMimeType(MimeTypes.APPLICATION_M3U8) @@ -687,11 +696,13 @@ private void clearAudioEffects() { audioEffectsMap.clear(); } - private DataSource.Factory buildDataSourceFactory() { - String userAgent = Util.getUserAgent(context, "just_audio"); - DataSource.Factory httpDataSourceFactory = new DefaultHttpDataSource.Factory() + private DataSource.Factory buildDataSourceFactory(Map headers) { + DefaultHttpDataSource.Factory httpDataSourceFactory = new DefaultHttpDataSource.Factory() .setUserAgent(userAgent) .setAllowCrossProtocolRedirects(true); + if (headers != null) { + httpDataSourceFactory.setDefaultRequestProperties(castToStringMap(headers)); + } return new DefaultDataSource.Factory(context, httpDataSourceFactory); } @@ -1036,6 +1047,15 @@ static Map mapOf(Object... args) { return map; } + static Map castToStringMap(Map map) { + if (map == null) return null; + Map map2 = new HashMap<>(); + for (Object key : map.keySet()) { + map2.put((String)key, (String)map.get(key)); + } + return map2; + } + enum ProcessingState { none, loading, diff --git a/just_audio/android/src/main/java/com/ryanheise/just_audio/MainMethodCallHandler.java b/just_audio/android/src/main/java/com/ryanheise/just_audio/MainMethodCallHandler.java index ac039d0f0..e4cea679d 100644 --- a/just_audio/android/src/main/java/com/ryanheise/just_audio/MainMethodCallHandler.java +++ b/just_audio/android/src/main/java/com/ryanheise/just_audio/MainMethodCallHandler.java @@ -34,7 +34,18 @@ public void onMethodCall(MethodCall call, @NonNull Result result) { break; } List rawAudioEffects = call.argument("androidAudioEffects"); - players.put(id, new AudioPlayer(applicationContext, messenger, id, call.argument("audioLoadConfiguration"), rawAudioEffects, call.argument("androidOffloadSchedulingEnabled"))); + players.put( + id, + new AudioPlayer( + applicationContext, + messenger, + id, + call.argument("audioLoadConfiguration"), + rawAudioEffects, + call.argument("androidOffloadSchedulingEnabled"), + call.argument("userAgent") + ) + ); result.success(null); break; } diff --git a/just_audio/example/pubspec.yaml b/just_audio/example/pubspec.yaml index 0df6b07ca..f187b4c71 100644 --- a/just_audio/example/pubspec.yaml +++ b/just_audio/example/pubspec.yaml @@ -3,7 +3,7 @@ description: Demonstrates how to use the just_audio plugin. publish_to: 'none' environment: - sdk: ">=2.14.0 <4.0.0" + sdk: ">=2.17.0 <4.0.0" dependencies: flutter: diff --git a/just_audio/lib/just_audio.dart b/just_audio/lib/just_audio.dart index ab40e74b1..cf083c634 100644 --- a/just_audio/lib/just_audio.dart +++ b/just_audio/lib/just_audio.dart @@ -1367,6 +1367,7 @@ class AudioPlayer { .toList() : [], androidOffloadSchedulingEnabled: _androidOffloadSchedulingEnabled, + userAgent: _userAgent, ))) : (_idlePlatform = _IdleAudioPlayer(id: _id, sequenceStream: sequenceStream)); @@ -1800,7 +1801,7 @@ class AudioLoadConfiguration { /// Speed control for live streams on Android. final AndroidLivePlaybackSpeedControl? androidLivePlaybackSpeedControl; - AudioLoadConfiguration({ + const AudioLoadConfiguration({ this.darwinLoadControl, this.androidLoadControl, this.androidLivePlaybackSpeedControl, @@ -1833,7 +1834,7 @@ class DarwinLoadControl { /// second. final double? preferredPeakBitRate; - DarwinLoadControl({ + const DarwinLoadControl({ this.automaticallyWaitsToMinimizeStalling = true, this.preferredForwardBufferDuration, this.canUseNetworkResourcesForLiveStreamingWhilePaused = false, @@ -1878,7 +1879,7 @@ class AndroidLoadControl { /// (Android) The back buffer duration. final Duration backBufferDuration; - AndroidLoadControl({ + const AndroidLoadControl({ this.minBufferDuration = const Duration(seconds: 50), this.maxBufferDuration = const Duration(seconds: 50), this.bufferForPlaybackDuration = const Duration(milliseconds: 2500), @@ -1931,7 +1932,7 @@ class AndroidLivePlaybackSpeedControl { /// achievable during playback. final double minPossibleLiveOffsetSmoothingFactor; - AndroidLivePlaybackSpeedControl({ + const AndroidLivePlaybackSpeedControl({ this.fallbackMinPlaybackSpeed = 0.97, this.fallbackMaxPlaybackSpeed = 1.03, this.minUpdateInterval = const Duration(seconds: 1), @@ -1956,6 +1957,54 @@ class AndroidLivePlaybackSpeedControl { ); } +class ProgressiveAudioSourceOptions { + final AndroidExtractorOptions? androidExtractorOptions; + // final DarwinAssetOptions? darwinAssetOptions; + + const ProgressiveAudioSourceOptions({ + this.androidExtractorOptions, + // this.darwinAssetOptions, + }); + + ProgressiveAudioSourceOptionsMessage _toMessage() => + ProgressiveAudioSourceOptionsMessage( + androidExtractorOptions: androidExtractorOptions?._toMessage(), + // darwinAssetOptions: darwinAssetOptions?._toMessage(), + ); +} + +// class DarwinAssetOptions { +// final bool preferPreciseDurationAndTiming; +// +// const DarwinAssetOptions({this.preferPreciseDurationAndTiming = false}); +// +// DarwinAssetOptionsMessage _toMessage() => DarwinAssetOptionsMessage( +// preferPreciseDurationAndTiming: preferPreciseDurationAndTiming, +// ); +// } + +class AndroidExtractorOptions { + static const flagMp3EnableIndexSeeking = 1 << 2; + static const flagMp3DisableId3Metadata = 1 << 3; + + final bool constantBitrateSeekingEnabled; + final bool constantBitrateSeekingAlwaysEnabled; + final int mp3Flags; + + const AndroidExtractorOptions({ + this.constantBitrateSeekingEnabled = true, + this.constantBitrateSeekingAlwaysEnabled = false, + this.mp3Flags = 0, + }); + + AndroidExtractorOptionsMessage _toMessage() => AndroidExtractorOptionsMessage( + constantBitrateSeekingEnabled: constantBitrateSeekingEnabled, + constantBitrateSeekingAlwaysEnabled: + constantBitrateSeekingAlwaysEnabled, + mp3Flags: mp3Flags, + ); +} + /// A local proxy HTTP server for making remote GET requests with headers. class _ProxyHttpServer { late HttpServer _server; @@ -2221,6 +2270,8 @@ abstract class UriAudioSource extends IndexedAudioSource { _overrideUri = await _loadAsset(uri.pathSegments.join('/')); } else if (uri.scheme != 'file' && !kIsWeb && + !_isAndroid() && + // !_isDarwin() && (headers != null || player._userAgent != null)) { await player._proxy.ensureRunning(); _overrideUri = player._proxy.addUriAudioSource(this); @@ -2289,13 +2340,24 @@ abstract class UriAudioSource extends IndexedAudioSource { /// If headers are set, just_audio will create a cleartext local HTTP proxy on /// your device to forward HTTP requests with headers included. class ProgressiveAudioSource extends UriAudioSource { - ProgressiveAudioSource(Uri uri, - {Map? headers, dynamic tag, Duration? duration}) - : super(uri, headers: headers, tag: tag, duration: duration); + final ProgressiveAudioSourceOptions? options; + + ProgressiveAudioSource( + super.uri, { + super.headers, + super.tag, + super.duration, + this.options, + }); @override AudioSourceMessage _toMessage() => ProgressiveAudioSourceMessage( - id: _id, uri: _effectiveUri.toString(), headers: headers, tag: tag); + id: _id, + uri: _effectiveUri.toString(), + headers: headers, + tag: tag, + options: options?._toMessage(), + ); } /// An [AudioSource] representing a DASH stream. The following URI schemes are @@ -2413,7 +2475,7 @@ class ConcatenatingAudioSource extends AudioSource { localInitialIndex = ci; } final childInitialIndex = - initialIndexWithinThisChild ? (initialIndex! - si) : null; + initialIndexWithinThisChild ? (initialIndex - si) : null; child._shuffle(initialIndex: childInitialIndex); si += childLength; } diff --git a/just_audio/pubspec.yaml b/just_audio/pubspec.yaml index 3465066a2..5d41e1248 100644 --- a/just_audio/pubspec.yaml +++ b/just_audio/pubspec.yaml @@ -10,7 +10,7 @@ topics: - background environment: - sdk: ">=2.14.0 <4.0.0" + sdk: ">=2.17.0 <4.0.0" flutter: ">=3.0.0" dependencies: diff --git a/just_audio/test/just_audio_test.dart b/just_audio/test/just_audio_test.dart index f84ced22e..207480e83 100644 --- a/just_audio/test/just_audio_test.dart +++ b/just_audio/test/just_audio_test.dart @@ -1293,7 +1293,7 @@ void runTests() { }); test('loadConfiguration', () async { - final audioLoadConfiguration = AudioLoadConfiguration( + const audioLoadConfiguration = AudioLoadConfiguration( darwinLoadControl: DarwinLoadControl(), androidLoadControl: AndroidLoadControl(), androidLivePlaybackSpeedControl: AndroidLivePlaybackSpeedControl(), diff --git a/just_audio_background/example/pubspec.yaml b/just_audio_background/example/pubspec.yaml index 53472ddcc..1618cfb96 100644 --- a/just_audio_background/example/pubspec.yaml +++ b/just_audio_background/example/pubspec.yaml @@ -3,7 +3,7 @@ description: Demonstrates how to use the just_audio plugin. publish_to: 'none' environment: - sdk: ">=2.14.0 <4.0.0" + sdk: ">=2.17.0 <4.0.0" dependencies: flutter: diff --git a/just_audio_platform_interface/lib/just_audio_platform_interface.dart b/just_audio_platform_interface/lib/just_audio_platform_interface.dart index 44da83ee5..687f5408d 100644 --- a/just_audio_platform_interface/lib/just_audio_platform_interface.dart +++ b/just_audio_platform_interface/lib/just_audio_platform_interface.dart @@ -397,6 +397,7 @@ class InitRequest { final List androidAudioEffects; final List darwinAudioEffects; final bool? androidOffloadSchedulingEnabled; + final String? userAgent; InitRequest({ required this.id, @@ -404,6 +405,7 @@ class InitRequest { this.androidAudioEffects = const [], this.darwinAudioEffects = const [], this.androidOffloadSchedulingEnabled, + this.userAgent, }); Map toMap() => { @@ -416,6 +418,7 @@ class InitRequest { .map((audioEffect) => audioEffect.toMap()) .toList(), 'androidOffloadSchedulingEnabled': androidOffloadSchedulingEnabled, + 'userAgent': userAgent, }; } @@ -1035,6 +1038,58 @@ class AndroidLivePlaybackSpeedControlMessage { }; } +/// Progressive audio source options to be communicated with the platform +/// implementation. +class ProgressiveAudioSourceOptionsMessage { + final AndroidExtractorOptionsMessage? androidExtractorOptions; + // final DarwinAssetOptionsMessage? darwinAssetOptions; + + const ProgressiveAudioSourceOptionsMessage({ + this.androidExtractorOptions, + // this.darwinAssetOptions, + }); + + Map toMap() => { + 'androidExtractorOptions': androidExtractorOptions?.toMap(), + // 'darwinAssetOptions': darwinAssetOptions?.toMap(), + }; +} + +/// Options for loading audio assets on iOS/macOS to be communicated with the +/// platform implementation. +// class DarwinAssetOptionsMessage { +// final bool preferPreciseDurationAndTiming; +// +// const DarwinAssetOptionsMessage({ +// required this.preferPreciseDurationAndTiming, +// }); +// +// Map toMap() => { +// 'preferPreciseDurationAndTiming': preferPreciseDurationAndTiming, +// }; +// } + +/// Options for extracting media files on Android to be communicated with the +/// platform implementation. +class AndroidExtractorOptionsMessage { + final bool constantBitrateSeekingEnabled; + final bool constantBitrateSeekingAlwaysEnabled; + final int mp3Flags; + + const AndroidExtractorOptionsMessage({ + required this.constantBitrateSeekingEnabled, + required this.constantBitrateSeekingAlwaysEnabled, + required this.mp3Flags, + }); + + Map toMap() => { + 'constantBitrateSeekingEnabled': constantBitrateSeekingEnabled, + 'constantBitrateSeekingAlwaysEnabled': + constantBitrateSeekingAlwaysEnabled, + 'mp3Flags': mp3Flags, + }; +} + /// Information about an audio source to be communicated with the platform /// implementation. abstract class AudioSourceMessage { @@ -1051,7 +1106,7 @@ abstract class IndexedAudioSourceMessage extends AudioSourceMessage { /// Since the tag type is unknown, this can only be used by platform /// implementations that pass by reference. final dynamic tag; - IndexedAudioSourceMessage({required String id, this.tag}) : super(id: id); + IndexedAudioSourceMessage({required super.id, this.tag}); } /// Information about a URI audio source to be communicated with the platform @@ -1061,22 +1116,25 @@ abstract class UriAudioSourceMessage extends IndexedAudioSourceMessage { final Map? headers; UriAudioSourceMessage({ - required String id, + required super.id, required this.uri, this.headers, - dynamic tag, - }) : super(id: id, tag: tag); + super.tag, + }); } /// Information about a progressive audio source to be communicated with the /// platform implementation. class ProgressiveAudioSourceMessage extends UriAudioSourceMessage { + final ProgressiveAudioSourceOptionsMessage? options; + ProgressiveAudioSourceMessage({ - required String id, - required String uri, - Map? headers, - dynamic tag, - }) : super(id: id, uri: uri, headers: headers, tag: tag); + required super.id, + required super.uri, + super.headers, + super.tag, + this.options, + }); @override Map toMap() => { @@ -1084,6 +1142,7 @@ class ProgressiveAudioSourceMessage extends UriAudioSourceMessage { 'id': id, 'uri': uri, 'headers': headers, + 'options': options?.toMap(), }; } @@ -1091,11 +1150,11 @@ class ProgressiveAudioSourceMessage extends UriAudioSourceMessage { /// implementation. class DashAudioSourceMessage extends UriAudioSourceMessage { DashAudioSourceMessage({ - required String id, - required String uri, - Map? headers, - dynamic tag, - }) : super(id: id, uri: uri, headers: headers, tag: tag); + required super.id, + required super.uri, + super.headers, + super.tag, + }); @override Map toMap() => { @@ -1110,11 +1169,11 @@ class DashAudioSourceMessage extends UriAudioSourceMessage { /// implementation. class HlsAudioSourceMessage extends UriAudioSourceMessage { HlsAudioSourceMessage({ - required String id, - required String uri, - Map? headers, - dynamic tag, - }) : super(id: id, uri: uri, headers: headers, tag: tag); + required super.id, + required super.uri, + super.headers, + super.tag, + }); @override Map toMap() => { @@ -1131,9 +1190,9 @@ class SilenceAudioSourceMessage extends IndexedAudioSourceMessage { final Duration duration; SilenceAudioSourceMessage({ - required String id, + required super.id, required this.duration, - }) : super(id: id); + }); @override Map toMap() => { @@ -1151,11 +1210,11 @@ class ConcatenatingAudioSourceMessage extends AudioSourceMessage { final List shuffleOrder; ConcatenatingAudioSourceMessage({ - required String id, + required super.id, required this.children, required this.useLazyPreparation, required this.shuffleOrder, - }) : super(id: id); + }); @override Map toMap() => { @@ -1175,12 +1234,12 @@ class ClippingAudioSourceMessage extends IndexedAudioSourceMessage { final Duration? end; ClippingAudioSourceMessage({ - required String id, + required super.id, required this.child, this.start, this.end, - dynamic tag, - }) : super(id: id, tag: tag); + super.tag, + }); @override Map toMap() => { @@ -1199,10 +1258,10 @@ class LoopingAudioSourceMessage extends AudioSourceMessage { final int count; LoopingAudioSourceMessage({ - required String id, + required super.id, required this.child, required this.count, - }) : super(id: id); + }); @override Map toMap() => { @@ -1326,9 +1385,9 @@ class AndroidLoudnessEnhancerMessage extends AudioEffectMessage { final double targetGain; AndroidLoudnessEnhancerMessage({ - required bool enabled, + required super.enabled, required this.targetGain, - }) : super(enabled: enabled); + }); @override Map toMap() => { @@ -1418,9 +1477,9 @@ class AndroidEqualizerMessage extends AudioEffectMessage { final AndroidEqualizerParametersMessage? parameters; AndroidEqualizerMessage({ - required bool enabled, + required super.enabled, required this.parameters, - }) : super(enabled: enabled); + }); @override Map toMap() => { diff --git a/just_audio_platform_interface/pubspec.yaml b/just_audio_platform_interface/pubspec.yaml index f11c159da..85724e062 100644 --- a/just_audio_platform_interface/pubspec.yaml +++ b/just_audio_platform_interface/pubspec.yaml @@ -17,5 +17,5 @@ dev_dependencies: mockito: ^5.0.0 environment: - sdk: ">=2.14.0 <4.0.0" + sdk: ">=2.17.0 <4.0.0" flutter: ">=3.0.0" diff --git a/just_audio_web/pubspec.yaml b/just_audio_web/pubspec.yaml index f7e176a5f..bb096d9b0 100644 --- a/just_audio_web/pubspec.yaml +++ b/just_audio_web/pubspec.yaml @@ -23,5 +23,5 @@ dev_dependencies: flutter_lints: ^2.0.1 environment: - sdk: ">=2.14.0 <4.0.0" + sdk: ">=2.17.0 <4.0.0" flutter: ">=3.0.0" From 977e81ddf3592a8ade6cc179f943b16217d21892 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Thu, 19 Oct 2023 02:09:32 +1100 Subject: [PATCH 12/64] Proxyless headers/user-agent on iOS/macOS. --- just_audio/CHANGELOG.md | 3 +- .../com/ryanheise/just_audio/AudioPlayer.java | 19 +++-- .../just_audio/MainMethodCallHandler.java | 3 +- just_audio/darwin/Classes/AudioPlayer.m | 10 +-- just_audio/darwin/Classes/UriAudioSource.m | 31 ++++++- just_audio/ios/Classes/UriAudioSource.h | 2 +- just_audio/lib/just_audio.dart | 84 +++++++++++++------ just_audio/macos/Classes/UriAudioSource.h | 2 +- .../lib/just_audio_platform_interface.dart | 31 ++++--- 9 files changed, 123 insertions(+), 62 deletions(-) diff --git a/just_audio/CHANGELOG.md b/just_audio/CHANGELOG.md index 30b25de53..e2a9c701f 100644 --- a/just_audio/CHANGELOG.md +++ b/just_audio/CHANGELOG.md @@ -2,8 +2,7 @@ * Add setAllowsExternalPlayback on iOS/macOS. * Support index-based seeking on Android. -* userAgent works without proxy on Android. -* headers work without proxy on Android. +* Add option to send headers/userAgent without proxy. ## 0.9.35 diff --git a/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java b/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java index 8dadebdb4..0f049d234 100644 --- a/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java +++ b/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java @@ -140,13 +140,11 @@ public AudioPlayer( final BinaryMessenger messenger, final String id, Map audioLoadConfiguration, List rawAudioEffects, - Boolean offloadSchedulingEnabled, - String userAgent + Boolean offloadSchedulingEnabled ) { this.context = applicationContext; this.rawAudioEffects = rawAudioEffects; this.offloadSchedulingEnabled = offloadSchedulingEnabled != null ? offloadSchedulingEnabled : false; - this.userAgent = userAgent != null ? userAgent : Util.getUserAgent(context, "just_audio"); methodChannel = new MethodChannel(messenger, "com.ryanheise.just_audio.methods." + id); methodChannel.setMethodCallHandler(this); eventChannel = new BetterEventChannel(messenger, "com.ryanheise.just_audio.events." + id); @@ -697,11 +695,22 @@ private void clearAudioEffects() { } private DataSource.Factory buildDataSourceFactory(Map headers) { + final Map stringHeaders = castToStringMap(headers); + String userAgent = null; + if (stringHeaders != null) { + userAgent = stringHeaders.removeKey("User-Agent"); + if (userAgent == null) { + userAgent = stringHeaders.removeKey("user-agent"); + } + } + if (userAgent == null) { + userAgent = Util.getUserAgent(context, "just_audio"); + } DefaultHttpDataSource.Factory httpDataSourceFactory = new DefaultHttpDataSource.Factory() .setUserAgent(userAgent) .setAllowCrossProtocolRedirects(true); - if (headers != null) { - httpDataSourceFactory.setDefaultRequestProperties(castToStringMap(headers)); + if (stringHeaders != null && stringHeaders.size() > 0) { + httpDataSourceFactory.setDefaultRequestProperties(stringHeaders); } return new DefaultDataSource.Factory(context, httpDataSourceFactory); } diff --git a/just_audio/android/src/main/java/com/ryanheise/just_audio/MainMethodCallHandler.java b/just_audio/android/src/main/java/com/ryanheise/just_audio/MainMethodCallHandler.java index e4cea679d..916423e97 100644 --- a/just_audio/android/src/main/java/com/ryanheise/just_audio/MainMethodCallHandler.java +++ b/just_audio/android/src/main/java/com/ryanheise/just_audio/MainMethodCallHandler.java @@ -42,8 +42,7 @@ public void onMethodCall(MethodCall call, @NonNull Result result) { id, call.argument("audioLoadConfiguration"), rawAudioEffects, - call.argument("androidOffloadSchedulingEnabled"), - call.argument("userAgent") + call.argument("androidOffloadSchedulingEnabled") ) ); result.success(null); diff --git a/just_audio/darwin/Classes/AudioPlayer.m b/just_audio/darwin/Classes/AudioPlayer.m index fbc70cdf4..8e16ec6ad 100644 --- a/just_audio/darwin/Classes/AudioPlayer.m +++ b/just_audio/darwin/Classes/AudioPlayer.m @@ -171,8 +171,8 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } else { result(FlutterMethodNotImplemented); } - } @catch (id exception) { - //NSLog(@"Error in handleMethodCall"); + } @catch (NSException *exception) { + //NSLog(@"%@", [exception callStackSymbols]); FlutterError *flutterError = [FlutterError errorWithCode:@"error" message:@"Error in handleMethodCall" details:nil]; result(flutterError); } @@ -453,11 +453,11 @@ - (void)metadataOutput:(AVPlayerItemMetadataOutput *)output didOutputTimedMetada - (AudioSource *)decodeAudioSource:(NSDictionary *)data { NSString *type = data[@"type"]; if ([@"progressive" isEqualToString:type]) { - return [[UriAudioSource alloc] initWithId:data[@"id"] uri:data[@"uri"] loadControl:_loadControl]; + return [[UriAudioSource alloc] initWithId:data[@"id"] uri:data[@"uri"] loadControl:_loadControl headers:data[@"headers"]]; } else if ([@"dash" isEqualToString:type]) { - return [[UriAudioSource alloc] initWithId:data[@"id"] uri:data[@"uri"] loadControl:_loadControl]; + return [[UriAudioSource alloc] initWithId:data[@"id"] uri:data[@"uri"] loadControl:_loadControl headers:data[@"headers"]]; } else if ([@"hls" isEqualToString:type]) { - return [[UriAudioSource alloc] initWithId:data[@"id"] uri:data[@"uri"] loadControl:_loadControl]; + return [[UriAudioSource alloc] initWithId:data[@"id"] uri:data[@"uri"] loadControl:_loadControl headers:data[@"headers"]]; } else if ([@"concatenating" isEqualToString:type]) { return [[ConcatenatingAudioSource alloc] initWithId:data[@"id"] audioSources:[self decodeAudioSources:data[@"children"]] diff --git a/just_audio/darwin/Classes/UriAudioSource.m b/just_audio/darwin/Classes/UriAudioSource.m index baccf6c41..1828e4a9f 100644 --- a/just_audio/darwin/Classes/UriAudioSource.m +++ b/just_audio/darwin/Classes/UriAudioSource.m @@ -10,13 +10,15 @@ @implementation UriAudioSource { IndexedPlayerItem *_playerItem2; /* CMTime _duration; */ LoadControl *_loadControl; + NSMutableDictionary *_headers; } -- (instancetype)initWithId:(NSString *)sid uri:(NSString *)uri loadControl:(LoadControl *)loadControl { +- (instancetype)initWithId:(NSString *)sid uri:(NSString *)uri loadControl:(LoadControl *)loadControl headers:(NSDictionary *)headers { self = [super initWithId:sid]; NSAssert(self, @"super init cannot be nil"); _uri = uri; _loadControl = loadControl; + _headers = headers ? [headers mutableCopy] : nil; _playerItem = [self createPlayerItem:uri]; _playerItem2 = nil; return self; @@ -31,7 +33,32 @@ - (IndexedPlayerItem *)createPlayerItem:(NSString *)uri { if ([uri hasPrefix:@"file://"]) { item = [[IndexedPlayerItem alloc] initWithURL:[NSURL fileURLWithPath:[[uri stringByRemovingPercentEncoding] substringFromIndex:7]]]; } else { - item = [[IndexedPlayerItem alloc] initWithURL:[NSURL URLWithString:uri]]; + NSMutableDictionary *options = [[NSMutableDictionary alloc] init]; + if (_headers) { + // Use user-agent key if it is the only header and the API is supported. + if ([_headers count] == 1) { + if (@available(macOS 13.0, iOS 16.0, *)) { + NSString *userAgent = _headers[@"User-Agent"]; + if (userAgent) { + [_headers removeObjectForKey:@"User-Agent"]; + } else { + userAgent = _headers[@"user-agent"]; + if (userAgent) { + [_headers removeObjectForKey:@"user-agent"]; + } + } + if (userAgent) { + options[AVURLAssetHTTPUserAgentKey] = userAgent; + } + } + } + if ([_headers count] > 0) { + options[@"AVURLAssetHTTPHeaderFieldsKey"] = _headers; + } + } + + AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:uri] options:options]; + item = [[IndexedPlayerItem alloc] initWithAsset:asset]; } if (@available(macOS 10.13, iOS 11.0, *)) { // This does the best at reducing distortion on voice with speeds below 1.0 diff --git a/just_audio/ios/Classes/UriAudioSource.h b/just_audio/ios/Classes/UriAudioSource.h index cd8ac49fc..efcad7192 100644 --- a/just_audio/ios/Classes/UriAudioSource.h +++ b/just_audio/ios/Classes/UriAudioSource.h @@ -6,6 +6,6 @@ @property (readonly, nonatomic) NSString *uri; -- (instancetype)initWithId:(NSString *)sid uri:(NSString *)uri loadControl:(LoadControl *)loadControl; +- (instancetype)initWithId:(NSString *)sid uri:(NSString *)uri loadControl:(LoadControl *)loadControl headers:(NSDictionary *)headers; @end diff --git a/just_audio/lib/just_audio.dart b/just_audio/lib/just_audio.dart index cf083c634..5c8073cac 100644 --- a/just_audio/lib/just_audio.dart +++ b/just_audio/lib/just_audio.dart @@ -58,6 +58,9 @@ class AudioPlayer { /// The user agent to set on all HTTP requests. final String? _userAgent; + /// Whether to use the proxy server to send request headers. + final bool _useProxyForRequestHeaders; + final AudioLoadConfiguration? _audioLoadConfiguration; final bool _androidOffloadSchedulingEnabled; @@ -143,14 +146,22 @@ class AudioPlayer { /// Creates an [AudioPlayer]. /// - /// Apps requesting remote URLs should specify a `[userAgent]` string with - /// this constructor which will be included in the `user-agent` header on all - /// HTTP requests (except on web where the browser's user agent will be sent). - /// This header helps to identify to the server which app is submitting the - /// request. If unspecified, it will default to Apple's Core Audio user agent - /// on iOS/macOS, or just_audio's user agent on Android. Note: this feature - /// is implemented via a local HTTP proxy which requires non-HTTPS support to - /// be enabled. See the README page for setup instructions. + /// Apps requesting remote URLs should set the [userAgent] parameter which + /// will be set as the `user-agent` header on all requests (except on web + /// where the browser's user agent will be used) to identify the client. If + /// unspecified, a platform-specific default will be supplied. + /// + /// Request headers including `user-agent` are sent by default via a local + /// HTTP proxy which requires non-HTTPS support to be enabled (see the README + /// page for setup instructions). Alternatively, you can set + /// [useProxyForRequestHeaders] to `false` to allow supported platforms to + /// send the request headers directly without use of the proxy. On iOS/macOS, + /// this will use the `AVURLAssetHTTPUserAgentKey` on iOS 16 and above, and + /// macOS 13 and above, if `user-agent` is the only header used. Otherwise, + /// the `AVURLAssetHTTPHeaderFieldsKey` key will be used. On Android, this + /// will use ExoPlayer's `setUserAgent` and `setDefaultRequestProperties`. + /// For Linux/Windows federated platform implementations, refer to the + /// documentation for that implementation's support. /// /// The player will automatically pause/duck and resume/unduck when audio /// interruptions occur (e.g. a phone call) or when headphones are unplugged. @@ -173,6 +184,7 @@ class AudioPlayer { AudioLoadConfiguration? audioLoadConfiguration, AudioPipeline? audioPipeline, bool androidOffloadSchedulingEnabled = false, + bool useProxyForRequestHeaders = true, }) : _id = _uuid.v4(), _userAgent = userAgent, _androidApplyAudioAttributes = @@ -180,7 +192,8 @@ class AudioPlayer { _handleAudioSessionActivation = handleAudioSessionActivation, _audioLoadConfiguration = audioLoadConfiguration, _audioPipeline = audioPipeline ?? AudioPipeline(), - _androidOffloadSchedulingEnabled = androidOffloadSchedulingEnabled { + _androidOffloadSchedulingEnabled = androidOffloadSchedulingEnabled, + _useProxyForRequestHeaders = useProxyForRequestHeaders { _audioPipeline._setup(this); if (_audioLoadConfiguration?.darwinLoadControl != null) { _automaticallyWaitsToMinimizeStalling = _audioLoadConfiguration! @@ -1367,7 +1380,6 @@ class AudioPlayer { .toList() : [], androidOffloadSchedulingEnabled: _androidOffloadSchedulingEnabled, - userAgent: _userAgent, ))) : (_idlePlatform = _IdleAudioPlayer(id: _id, sequenceStream: sequenceStream)); @@ -1959,29 +1971,29 @@ class AndroidLivePlaybackSpeedControl { class ProgressiveAudioSourceOptions { final AndroidExtractorOptions? androidExtractorOptions; - // final DarwinAssetOptions? darwinAssetOptions; + final DarwinAssetOptions? darwinAssetOptions; const ProgressiveAudioSourceOptions({ this.androidExtractorOptions, - // this.darwinAssetOptions, + this.darwinAssetOptions, }); ProgressiveAudioSourceOptionsMessage _toMessage() => ProgressiveAudioSourceOptionsMessage( androidExtractorOptions: androidExtractorOptions?._toMessage(), - // darwinAssetOptions: darwinAssetOptions?._toMessage(), + darwinAssetOptions: darwinAssetOptions?._toMessage(), ); } -// class DarwinAssetOptions { -// final bool preferPreciseDurationAndTiming; -// -// const DarwinAssetOptions({this.preferPreciseDurationAndTiming = false}); -// -// DarwinAssetOptionsMessage _toMessage() => DarwinAssetOptionsMessage( -// preferPreciseDurationAndTiming: preferPreciseDurationAndTiming, -// ); -// } +class DarwinAssetOptions { + final bool preferPreciseDurationAndTiming; + + const DarwinAssetOptions({this.preferPreciseDurationAndTiming = false}); + + DarwinAssetOptionsMessage _toMessage() => DarwinAssetOptionsMessage( + preferPreciseDurationAndTiming: preferPreciseDurationAndTiming, + ); +} class AndroidExtractorOptions { static const flagMp3EnableIndexSeeking = 1 << 2; @@ -2208,6 +2220,8 @@ abstract class AudioSource { player._registerAudioSource(this); } + String? get _userAgent => _player?._userAgent; + void _shuffle({int? initialIndex}); @mustCallSuper @@ -2263,6 +2277,15 @@ abstract class UriAudioSource extends IndexedAudioSource { /// [uri]. Uri get _effectiveUri => _overrideUri ?? uri; + Map? get _mergedHeaders => + headers == null && _userAgent == null + ? null + : { + if (headers != null) + for (var key in headers!.keys) key: headers![key]!, + if (_userAgent != null) 'User-Agent': _userAgent!, + }; + @override Future _setup(AudioPlayer player) async { await super._setup(player); @@ -2270,8 +2293,7 @@ abstract class UriAudioSource extends IndexedAudioSource { _overrideUri = await _loadAsset(uri.pathSegments.join('/')); } else if (uri.scheme != 'file' && !kIsWeb && - !_isAndroid() && - // !_isDarwin() && + player._useProxyForRequestHeaders && (headers != null || player._userAgent != null)) { await player._proxy.ensureRunning(); _overrideUri = player._proxy.addUriAudioSource(this); @@ -2354,7 +2376,7 @@ class ProgressiveAudioSource extends UriAudioSource { AudioSourceMessage _toMessage() => ProgressiveAudioSourceMessage( id: _id, uri: _effectiveUri.toString(), - headers: headers, + headers: _mergedHeaders, tag: tag, options: options?._toMessage(), ); @@ -2381,7 +2403,11 @@ class DashAudioSource extends UriAudioSource { @override AudioSourceMessage _toMessage() => DashAudioSourceMessage( - id: _id, uri: _effectiveUri.toString(), headers: headers, tag: tag); + id: _id, + uri: _effectiveUri.toString(), + headers: _mergedHeaders, + tag: tag, + ); } /// An [AudioSource] representing an HLS stream. The following URI schemes are @@ -2404,7 +2430,11 @@ class HlsAudioSource extends UriAudioSource { @override AudioSourceMessage _toMessage() => HlsAudioSourceMessage( - id: _id, uri: _effectiveUri.toString(), headers: headers, tag: tag); + id: _id, + uri: _effectiveUri.toString(), + headers: _mergedHeaders, + tag: tag, + ); } /// An [AudioSource] for a period of silence. diff --git a/just_audio/macos/Classes/UriAudioSource.h b/just_audio/macos/Classes/UriAudioSource.h index 3a06380d6..520e60dab 100644 --- a/just_audio/macos/Classes/UriAudioSource.h +++ b/just_audio/macos/Classes/UriAudioSource.h @@ -6,6 +6,6 @@ @property (readonly, nonatomic) NSString *uri; -- (instancetype)initWithId:(NSString *)sid uri:(NSString *)uri loadControl:(LoadControl *)loadControl; +- (instancetype)initWithId:(NSString *)sid uri:(NSString *)uri loadControl:(LoadControl *)loadControl headers:(NSDictionary *)headers; @end diff --git a/just_audio_platform_interface/lib/just_audio_platform_interface.dart b/just_audio_platform_interface/lib/just_audio_platform_interface.dart index 687f5408d..5b585174c 100644 --- a/just_audio_platform_interface/lib/just_audio_platform_interface.dart +++ b/just_audio_platform_interface/lib/just_audio_platform_interface.dart @@ -397,7 +397,6 @@ class InitRequest { final List androidAudioEffects; final List darwinAudioEffects; final bool? androidOffloadSchedulingEnabled; - final String? userAgent; InitRequest({ required this.id, @@ -405,7 +404,6 @@ class InitRequest { this.androidAudioEffects = const [], this.darwinAudioEffects = const [], this.androidOffloadSchedulingEnabled, - this.userAgent, }); Map toMap() => { @@ -418,7 +416,6 @@ class InitRequest { .map((audioEffect) => audioEffect.toMap()) .toList(), 'androidOffloadSchedulingEnabled': androidOffloadSchedulingEnabled, - 'userAgent': userAgent, }; } @@ -1042,32 +1039,32 @@ class AndroidLivePlaybackSpeedControlMessage { /// implementation. class ProgressiveAudioSourceOptionsMessage { final AndroidExtractorOptionsMessage? androidExtractorOptions; - // final DarwinAssetOptionsMessage? darwinAssetOptions; + final DarwinAssetOptionsMessage? darwinAssetOptions; const ProgressiveAudioSourceOptionsMessage({ this.androidExtractorOptions, - // this.darwinAssetOptions, + this.darwinAssetOptions, }); Map toMap() => { 'androidExtractorOptions': androidExtractorOptions?.toMap(), - // 'darwinAssetOptions': darwinAssetOptions?.toMap(), + 'darwinAssetOptions': darwinAssetOptions?.toMap(), }; } /// Options for loading audio assets on iOS/macOS to be communicated with the /// platform implementation. -// class DarwinAssetOptionsMessage { -// final bool preferPreciseDurationAndTiming; -// -// const DarwinAssetOptionsMessage({ -// required this.preferPreciseDurationAndTiming, -// }); -// -// Map toMap() => { -// 'preferPreciseDurationAndTiming': preferPreciseDurationAndTiming, -// }; -// } +class DarwinAssetOptionsMessage { + final bool preferPreciseDurationAndTiming; + + const DarwinAssetOptionsMessage({ + required this.preferPreciseDurationAndTiming, + }); + + Map toMap() => { + 'preferPreciseDurationAndTiming': preferPreciseDurationAndTiming, + }; +} /// Options for extracting media files on Android to be communicated with the /// platform implementation. From fcf67d31fc44ee18dc9e40b91bfef5a1a6b6e9cb Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Thu, 19 Oct 2023 12:42:38 +1100 Subject: [PATCH 13/64] Java compile error. --- .../src/main/java/com/ryanheise/just_audio/AudioPlayer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java b/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java index 0f049d234..aa8fa3ea9 100644 --- a/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java +++ b/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java @@ -698,9 +698,9 @@ private DataSource.Factory buildDataSourceFactory(Map headers) { final Map stringHeaders = castToStringMap(headers); String userAgent = null; if (stringHeaders != null) { - userAgent = stringHeaders.removeKey("User-Agent"); + userAgent = stringHeaders.remove("User-Agent"); if (userAgent == null) { - userAgent = stringHeaders.removeKey("user-agent"); + userAgent = stringHeaders.remove("user-agent"); } } if (userAgent == null) { From cb1be0ac325b0a2c89fe63bbb65762fc954c9c2c Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Thu, 19 Oct 2023 12:46:38 +1100 Subject: [PATCH 14/64] CHANGELOG --- just_audio/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/just_audio/CHANGELOG.md b/just_audio/CHANGELOG.md index e2a9c701f..358b147fe 100644 --- a/just_audio/CHANGELOG.md +++ b/just_audio/CHANGELOG.md @@ -1,7 +1,7 @@ ## 0.9.36 * Add setAllowsExternalPlayback on iOS/macOS. -* Support index-based seeking on Android. +* Support index-based seeking on Android/iOS/macOS. * Add option to send headers/userAgent without proxy. ## 0.9.35 From 8a5fd2e5ea5c15862a5ea6661158f13c7dc0d00f Mon Sep 17 00:00:00 2001 From: Cris Edgar Date: Thu, 26 Oct 2023 00:58:08 +0100 Subject: [PATCH 15/64] Add test to check that supplied request headers aren't overwritten by proxy handler --- just_audio/test/just_audio_test.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/just_audio/test/just_audio_test.dart b/just_audio/test/just_audio_test.dart index f84ced22e..d665711e0 100644 --- a/just_audio/test/just_audio_test.dart +++ b/just_audio/test/just_audio_test.dart @@ -397,9 +397,10 @@ void runTests() { await server.start(); final player = AudioPlayer(); // This simulates an actual URL + var headers = {'custom-header': 'Hello', 'accept-encoding' : 'custom-encoding'}; final uri = Uri.parse( 'http://${InternetAddress.loopbackIPv4.address}:${server.port}/proxy/foo.mp3'); - await player.setUrl('$uri', headers: {'custom-header': 'Hello'}); + await player.setUrl('$uri', headers: headers); // Obtain the proxy URL that the platform side should use to load the data. final proxyUri = Uri.parse(player.icyMetadata!.info!.url!); // Simulate the platform side requesting the data. @@ -410,6 +411,8 @@ void runTests() { expect(responseText, equals('Hello')); expect(response.headers.value(HttpHeaders.contentTypeHeader), equals('audio/mock')); + //MockWebServer returns original request headers in response headers (with 'original_' prepended) + headers.forEach((key, value) => expect(response.headers.value('original_$key'),value)); await server.stop(); await player.dispose(); }); @@ -1764,6 +1767,7 @@ class MockWebServer { response.contentLength = body.length; response.statusCode = HttpStatus.ok; response.headers.set(HttpHeaders.contentTypeHeader, 'audio/mock'); + request.headers.forEach((name, value) => response.headers.set('original_$name', value)); response.add(body); await response.flush(); await response.close(); From a491154948cb1743cbcb5d2d59940e9db4e5ed29 Mon Sep 17 00:00:00 2001 From: Cris Edgar Date: Thu, 26 Oct 2023 00:59:07 +0100 Subject: [PATCH 16/64] Ensure user supplied headers are written on top of default headers --- just_audio/lib/just_audio.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/just_audio/lib/just_audio.dart b/just_audio/lib/just_audio.dart index 60cb5e67b..83078a8d0 100644 --- a/just_audio/lib/just_audio.dart +++ b/just_audio/lib/just_audio.dart @@ -3170,9 +3170,11 @@ _ProxyHandler _proxyHandlerForUri( // Try to make normal request String? host; try { - final requestHeaders = {if (headers != null) ...headers}; + final requestHeaders = {}; request.headers .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 = await _getUrl(client, redirectedUri ?? uri, headers: requestHeaders); host = originRequest.headers.value(HttpHeaders.hostHeader); From b0981aa7c61a3adb491cfc321fc9e1c70ec59d0b Mon Sep 17 00:00:00 2001 From: Cris Edgar Date: Thu, 26 Oct 2023 01:33:30 +0100 Subject: [PATCH 17/64] Bump version and desc for #1100 in change log --- just_audio/CHANGELOG.md | 4 ++++ just_audio/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/just_audio/CHANGELOG.md b/just_audio/CHANGELOG.md index e1b548570..be34df022 100644 --- a/just_audio/CHANGELOG.md +++ b/just_audio/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.36 + +* Fix bug where user supplied headers are overwritten by defaults (@ctedgar). + ## 0.9.35 * Fix nullable completer argument type (@srawlins). diff --git a/just_audio/pubspec.yaml b/just_audio/pubspec.yaml index 53b32e2bf..f443f3c70 100644 --- a/just_audio/pubspec.yaml +++ b/just_audio/pubspec.yaml @@ -1,6 +1,6 @@ name: just_audio description: A feature-rich audio player for Flutter. Loop, clip and concatenate any sound from any source (asset/file/URL/stream) in a variety of audio formats with gapless playback. -version: 0.9.35 +version: 0.9.36 repository: https://github.com/ryanheise/just_audio/tree/minor/just_audio issue_tracker: https://github.com/ryanheise/just_audio/issues topics: From c8ab8a6469daf12c9583870efbe394ea16bcc9fb Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Thu, 9 Nov 2023 23:17:58 +1100 Subject: [PATCH 18/64] Fix NSNull vs nil check. --- just_audio/darwin/Classes/UriAudioSource.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/just_audio/darwin/Classes/UriAudioSource.m b/just_audio/darwin/Classes/UriAudioSource.m index 1828e4a9f..759dd990b 100644 --- a/just_audio/darwin/Classes/UriAudioSource.m +++ b/just_audio/darwin/Classes/UriAudioSource.m @@ -18,7 +18,7 @@ - (instancetype)initWithId:(NSString *)sid uri:(NSString *)uri loadControl:(Load NSAssert(self, @"super init cannot be nil"); _uri = uri; _loadControl = loadControl; - _headers = headers ? [headers mutableCopy] : nil; + _headers = headers != (id)[NSNull null] ? [headers mutableCopy] : nil; _playerItem = [self createPlayerItem:uri]; _playerItem2 = nil; return self; From 791680227663dd7ea5865509d1fcffb4139bf554 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Sat, 11 Nov 2023 19:35:37 +1100 Subject: [PATCH 19/64] Mention useProxyForRequestHeaders in the README. --- just_audio/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/just_audio/README.md b/just_audio/README.md index 5027b164e..1f3339cb9 100644 --- a/just_audio/README.md +++ b/just_audio/README.md @@ -105,6 +105,8 @@ final duration = await player.setUrl('https://foo.com/bar.mp3', headers: {'header1': 'value1', 'header2': 'value2'}); ``` +Note: By default, headers are implemented via a local HTTP proxy which on Android, iOS and macOS requires non-HTTPS support to be enabled. See [Platform Specific Configuration](#platform-specific-configuration). To use the platform's native implementation of headers instead of the proxy, pass `useProxyForRequestHeaders: false` into the `AudioPlayer` constructor. On iOS/macOS, this will use the undocumented `AVURLAssetHTTPHeaderFieldsKey` API which may present issues for App Store submission. + ### Working with caches ```dart @@ -240,7 +242,7 @@ If you wish to connect to non-HTTPS URLs (typically HTTP), also add the followin ``` -Note that just_audio's proxy (used to implement features such as caching and stream audio sources) runs on a `localhost` HTTP server, and this also requires cleartext access to be enabled. You can either enable this via the option above which also enables access to any non-HTTPS URL, or you can instead limit cleartext access to just `localhost` URLs by defining a network security config. To use this approach, create the file `android/app/src/main/res/xml/network_security_config.xml`: +Note that just_audio's proxy (used to implement features such as headers, caching and stream audio sources) runs on a `localhost` HTTP server, and this also requires cleartext access to be enabled. You can either enable this via the option above which also enables access to any non-HTTPS URL, or you can instead limit cleartext access to just `localhost` URLs by defining a network security config. To use this approach, create the file `android/app/src/main/res/xml/network_security_config.xml`: ```xml @@ -299,7 +301,7 @@ post_install do |installer| end ``` -If you wish to connect to non-HTTPS URLs, or if you use a feature that depends on the proxy such as caching or stream audio sources, add the following to your `Info.plist` file: +If you wish to connect to non-HTTPS URLs, or if you use a feature that depends on the proxy such as headers, caching or stream audio sources, add the following to your `Info.plist` file: ```xml NSAppTransportSecurity @@ -320,7 +322,7 @@ To allow your macOS application to access audio files on the Internet, add the f ``` -If you wish to connect to non-HTTPS URLs, or if you use a feature that depends on the proxy such as caching or stream audio sources, add the following to your `Info.plist` file: +If you wish to connect to non-HTTPS URLs, or if you use a feature that depends on the proxy such as headers, caching or stream audio sources, add the following to your `Info.plist` file: ```xml NSAppTransportSecurity From de7a461c95217048ca614b5103d93c09cb0c69df Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Sun, 12 Nov 2023 21:07:12 +1100 Subject: [PATCH 20/64] Implement index-based seeking on Android. --- .../com/ryanheise/just_audio/AudioPlayer.java | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java b/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java index aa8fa3ea9..7837d86b0 100644 --- a/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java +++ b/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java @@ -77,8 +77,6 @@ public class AudioPlayer implements MethodCallHandler, Player.Listener, Metadata private long updatePosition; private long updateTime; private long bufferedPosition; - private Long start; - private Long end; private Long seekPos; private long initialPos; private Integer initialIndex; @@ -92,7 +90,6 @@ public class AudioPlayer implements MethodCallHandler, Player.Listener, Metadata private AudioAttributes pendingAudioAttributes; private LoadControl loadControl; private boolean offloadSchedulingEnabled; - private String userAgent; private LivePlaybackSpeedControl livePlaybackSpeedControl; private List rawAudioEffects; private List audioEffects = new ArrayList(); @@ -101,7 +98,6 @@ public class AudioPlayer implements MethodCallHandler, Player.Listener, Metadata private Map pendingPlaybackEvent; private ExoPlayer player; - private DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory(); private Integer audioSessionId; private MediaSource mediaSource; private Integer currentIndex; @@ -138,7 +134,8 @@ public void run() { public AudioPlayer( final Context applicationContext, final BinaryMessenger messenger, - final String id, Map audioLoadConfiguration, + final String id, + Map audioLoadConfiguration, List rawAudioEffects, Boolean offloadSchedulingEnabled ) { @@ -150,7 +147,6 @@ public AudioPlayer( eventChannel = new BetterEventChannel(messenger, "com.ryanheise.just_audio.events." + id); dataEventChannel = new BetterEventChannel(messenger, "com.ryanheise.just_audio.data." + id); processingState = ProcessingState.none; - extractorsFactory.setConstantBitrateSeekingEnabled(true); if (audioLoadConfiguration != null) { Map loadControlMap = (Map)audioLoadConfiguration.get("androidLoadControl"); if (loadControlMap != null) { @@ -595,12 +591,31 @@ private MediaSource getAudioSource(final Object json) { return mediaSource; } + private DefaultExtractorsFactory buildExtractorsFactory(Map options) { + DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory(); + boolean constantBitrateSeekingEnabled = true; + boolean constantBitrateSeekingAlwaysEnabled = false; + int mp3Flags = 0; + if (options != null) { + Map androidExtractorOptions = (Map)options.get("androidExtractorOptions"); + if (androidExtractorOptions != null) { + constantBitrateSeekingEnabled = (Boolean)androidExtractorOptions.get("constantBitrateSeekingEnabled"); + constantBitrateSeekingAlwaysEnabled = (Boolean)androidExtractorOptions.get("constantBitrateSeekingAlwaysEnabled"); + mp3Flags = (Integer)androidExtractorOptions.get("mp3Flags"); + } + } + extractorsFactory.setConstantBitrateSeekingEnabled(constantBitrateSeekingEnabled); + extractorsFactory.setConstantBitrateSeekingAlwaysEnabled(constantBitrateSeekingAlwaysEnabled); + extractorsFactory.setMp3ExtractorFlags(mp3Flags); + return extractorsFactory; + } + private MediaSource decodeAudioSource(final Object json) { Map map = (Map)json; String id = (String)map.get("id"); switch ((String)map.get("type")) { case "progressive": - return new ProgressiveMediaSource.Factory(buildDataSourceFactory(mapGet(map, "headers")), extractorsFactory) + return new ProgressiveMediaSource.Factory(buildDataSourceFactory(mapGet(map, "headers")), buildExtractorsFactory(mapGet(map, "options"))) .createMediaSource(new MediaItem.Builder() .setUri(Uri.parse((String)map.get("uri"))) .setTag(id) From 93abeba0d891319d2163ed7f798f43974e3167b3 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Sun, 12 Nov 2023 23:28:19 +1100 Subject: [PATCH 21/64] Implement preferPreciseDurationAndTiming on iOS/macOS. --- just_audio/darwin/Classes/AudioPlayer.m | 6 +++--- just_audio/darwin/Classes/UriAudioSource.m | 18 +++++++++++++----- just_audio/ios/Classes/UriAudioSource.h | 2 +- just_audio/macos/Classes/UriAudioSource.h | 2 +- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/just_audio/darwin/Classes/AudioPlayer.m b/just_audio/darwin/Classes/AudioPlayer.m index 8e16ec6ad..c742f595a 100644 --- a/just_audio/darwin/Classes/AudioPlayer.m +++ b/just_audio/darwin/Classes/AudioPlayer.m @@ -453,11 +453,11 @@ - (void)metadataOutput:(AVPlayerItemMetadataOutput *)output didOutputTimedMetada - (AudioSource *)decodeAudioSource:(NSDictionary *)data { NSString *type = data[@"type"]; if ([@"progressive" isEqualToString:type]) { - return [[UriAudioSource alloc] initWithId:data[@"id"] uri:data[@"uri"] loadControl:_loadControl headers:data[@"headers"]]; + return [[UriAudioSource alloc] initWithId:data[@"id"] uri:data[@"uri"] loadControl:_loadControl headers:data[@"headers"] options:data[@"options"]]; } else if ([@"dash" isEqualToString:type]) { - return [[UriAudioSource alloc] initWithId:data[@"id"] uri:data[@"uri"] loadControl:_loadControl headers:data[@"headers"]]; + return [[UriAudioSource alloc] initWithId:data[@"id"] uri:data[@"uri"] loadControl:_loadControl headers:data[@"headers"] options:data[@"options"]]; } else if ([@"hls" isEqualToString:type]) { - return [[UriAudioSource alloc] initWithId:data[@"id"] uri:data[@"uri"] loadControl:_loadControl headers:data[@"headers"]]; + return [[UriAudioSource alloc] initWithId:data[@"id"] uri:data[@"uri"] loadControl:_loadControl headers:data[@"headers"] options:data[@"options"]]; } else if ([@"concatenating" isEqualToString:type]) { return [[ConcatenatingAudioSource alloc] initWithId:data[@"id"] audioSources:[self decodeAudioSources:data[@"children"]] diff --git a/just_audio/darwin/Classes/UriAudioSource.m b/just_audio/darwin/Classes/UriAudioSource.m index 759dd990b..28029daf5 100644 --- a/just_audio/darwin/Classes/UriAudioSource.m +++ b/just_audio/darwin/Classes/UriAudioSource.m @@ -11,14 +11,16 @@ @implementation UriAudioSource { /* CMTime _duration; */ LoadControl *_loadControl; NSMutableDictionary *_headers; + NSDictionary *_options; } -- (instancetype)initWithId:(NSString *)sid uri:(NSString *)uri loadControl:(LoadControl *)loadControl headers:(NSDictionary *)headers { +- (instancetype)initWithId:(NSString *)sid uri:(NSString *)uri loadControl:(LoadControl *)loadControl headers:(NSDictionary *)headers options:(NSDictionary *)options { self = [super initWithId:sid]; NSAssert(self, @"super init cannot be nil"); _uri = uri; _loadControl = loadControl; _headers = headers != (id)[NSNull null] ? [headers mutableCopy] : nil; + _options = options; _playerItem = [self createPlayerItem:uri]; _playerItem2 = nil; return self; @@ -33,7 +35,7 @@ - (IndexedPlayerItem *)createPlayerItem:(NSString *)uri { if ([uri hasPrefix:@"file://"]) { item = [[IndexedPlayerItem alloc] initWithURL:[NSURL fileURLWithPath:[[uri stringByRemovingPercentEncoding] substringFromIndex:7]]]; } else { - NSMutableDictionary *options = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *assetOptions = [[NSMutableDictionary alloc] init]; if (_headers) { // Use user-agent key if it is the only header and the API is supported. if ([_headers count] == 1) { @@ -48,16 +50,22 @@ - (IndexedPlayerItem *)createPlayerItem:(NSString *)uri { } } if (userAgent) { - options[AVURLAssetHTTPUserAgentKey] = userAgent; + assetOptions[AVURLAssetHTTPUserAgentKey] = userAgent; } } } if ([_headers count] > 0) { - options[@"AVURLAssetHTTPHeaderFieldsKey"] = _headers; + assetOptions[@"AVURLAssetHTTPHeaderFieldsKey"] = _headers; + } + } + if (_options != (id)[NSNull null]) { + NSDictionary *darwinOptions = _options[@"darwinAssetOptions"]; + if (darwinOptions != (id)[NSNull null]) { + assetOptions[AVURLAssetPreferPreciseDurationAndTimingKey] = darwinOptions[@"preferPreciseDurationAndTiming"]; } } - AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:uri] options:options]; + AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:uri] options:assetOptions]; item = [[IndexedPlayerItem alloc] initWithAsset:asset]; } if (@available(macOS 10.13, iOS 11.0, *)) { diff --git a/just_audio/ios/Classes/UriAudioSource.h b/just_audio/ios/Classes/UriAudioSource.h index efcad7192..1e4f92a6f 100644 --- a/just_audio/ios/Classes/UriAudioSource.h +++ b/just_audio/ios/Classes/UriAudioSource.h @@ -6,6 +6,6 @@ @property (readonly, nonatomic) NSString *uri; -- (instancetype)initWithId:(NSString *)sid uri:(NSString *)uri loadControl:(LoadControl *)loadControl headers:(NSDictionary *)headers; +- (instancetype)initWithId:(NSString *)sid uri:(NSString *)uri loadControl:(LoadControl *)loadControl headers:(NSDictionary *)headers options:(NSDictionary *)options; @end diff --git a/just_audio/macos/Classes/UriAudioSource.h b/just_audio/macos/Classes/UriAudioSource.h index 520e60dab..6e771e5af 100644 --- a/just_audio/macos/Classes/UriAudioSource.h +++ b/just_audio/macos/Classes/UriAudioSource.h @@ -6,6 +6,6 @@ @property (readonly, nonatomic) NSString *uri; -- (instancetype)initWithId:(NSString *)sid uri:(NSString *)uri loadControl:(LoadControl *)loadControl headers:(NSDictionary *)headers; +- (instancetype)initWithId:(NSString *)sid uri:(NSString *)uri loadControl:(LoadControl *)loadControl headers:(NSDictionary *)headers options:(NSDictionary *)options; @end From b9c81230cd0d55e77cf00e08934870d9fd1ae8f4 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Sun, 12 Nov 2023 22:51:30 +1100 Subject: [PATCH 22/64] Fix Android NPE in getDuration() --- .../src/main/java/com/ryanheise/just_audio/AudioPlayer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java b/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java index 7837d86b0..a3d12e224 100644 --- a/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java +++ b/just_audio/android/src/main/java/com/ryanheise/just_audio/AudioPlayer.java @@ -906,7 +906,7 @@ private long getCurrentPosition() { } private long getDuration() { - if (processingState == ProcessingState.none || processingState == ProcessingState.loading) { + if (processingState == ProcessingState.none || processingState == ProcessingState.loading || player == null) { return C.TIME_UNSET; } else { return player.getDuration(); From ef749675544b807a297e0a0d2cefb0ce4c1c015e Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Sun, 12 Nov 2023 22:59:04 +1100 Subject: [PATCH 23/64] Update platform interface CHANGELOG --- just_audio_platform_interface/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/just_audio_platform_interface/CHANGELOG.md b/just_audio_platform_interface/CHANGELOG.md index d908dfe03..e101b03f3 100644 --- a/just_audio_platform_interface/CHANGELOG.md +++ b/just_audio_platform_interface/CHANGELOG.md @@ -1,6 +1,7 @@ ## 4.2.2 * Add setAllowsExternalPlayback on iOS/macOS. +* Support index-based seeking on Android/iOS/macOS. ## 4.2.1 From 819df6413cb4d896553baba5eb41248f174658b0 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Mon, 13 Nov 2023 00:22:36 +1100 Subject: [PATCH 24/64] Pass through missing methods in just_audio_background. --- just_audio/example/android/app/build.gradle | 2 +- just_audio_background/CHANGELOG.md | 4 + .../example/android/app/build.gradle | 4 +- .../lib/just_audio_background.dart | 99 ++++++++++++++++++- just_audio_background/pubspec.yaml | 6 +- 5 files changed, 108 insertions(+), 7 deletions(-) diff --git a/just_audio/example/android/app/build.gradle b/just_audio/example/android/app/build.gradle index b786c27cd..2c1c06f3f 100644 --- a/just_audio/example/android/app/build.gradle +++ b/just_audio/example/android/app/build.gradle @@ -36,7 +36,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.ryanheise.just_audio_example" minSdkVersion 19 - targetSdkVersion 31 + targetSdkVersion 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/just_audio_background/CHANGELOG.md b/just_audio_background/CHANGELOG.md index 6b39c7cdc..4035a212c 100644 --- a/just_audio_background/CHANGELOG.md +++ b/just_audio_background/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.1-beta.11 + +* Pass through missing API methods. + ## 0.0.1-beta.10 * Fix bug where AudioPlayer constructor parameters were ignored. diff --git a/just_audio_background/example/android/app/build.gradle b/just_audio_background/example/android/app/build.gradle index b59d20728..34247fd11 100644 --- a/just_audio_background/example/android/app/build.gradle +++ b/just_audio_background/example/android/app/build.gradle @@ -25,7 +25,7 @@ apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 31 + compileSdkVersion 33 lintOptions { disable 'InvalidPackage' @@ -35,7 +35,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.ryanheise.just_audio_example" minSdkVersion 19 - targetSdkVersion 31 + targetSdkVersion 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/just_audio_background/lib/just_audio_background.dart b/just_audio_background/lib/just_audio_background.dart index ae875b5f4..17caf5897 100644 --- a/just_audio_background/lib/just_audio_background.dart +++ b/just_audio_background/lib/just_audio_background.dart @@ -254,10 +254,23 @@ class _JustAudioPlayer extends AudioPlayerPlatform { @override Future setSpeed(SetSpeedRequest request) async { - await _audioHandler.setSpeed(request.speed); + await _playerAudioHandler.setSpeed(request.speed); return SetSpeedResponse(); } + @override + Future setPitch(SetPitchRequest request) async { + await _playerAudioHandler.customSetPitch(request); + return SetPitchResponse(); + } + + @override + Future setSkipSilence( + SetSkipSilenceRequest request) async { + await _playerAudioHandler.customSetSkipSilence(request); + return SetSkipSilenceResponse(); + } + @override Future setLoopMode(SetLoopModeRequest request) async { await _audioHandler @@ -308,6 +321,47 @@ class _JustAudioPlayer extends AudioPlayerPlatform { SetAutomaticallyWaitsToMinimizeStallingRequest request) => _playerAudioHandler .customSetAutomaticallyWaitsToMinimizeStalling(request); + + @override + Future androidEqualizerBandSetGain( + AndroidEqualizerBandSetGainRequest request) => + _playerAudioHandler.customAndroidEqualizerBandSetGain(request); + + @override + Future androidEqualizerGetParameters( + AndroidEqualizerGetParametersRequest request) => + _playerAudioHandler.customAndroidEqualizerGetParameters(request); + + @override + Future + androidLoudnessEnhancerSetTargetGain( + AndroidLoudnessEnhancerSetTargetGainRequest request) => + _playerAudioHandler + .customAndroidLoudnessEnhancerSetTargetGain(request); + + @override + Future audioEffectSetEnabled( + AudioEffectSetEnabledRequest request) => + _playerAudioHandler.customAudioEffectSetEnabled(request); + + @override + Future setAllowsExternalPlayback( + SetAllowsExternalPlaybackRequest request) => + _playerAudioHandler.customSetAllowsExternalPlayback(request); + + @override + Future + setCanUseNetworkResourcesForLiveStreamingWhilePaused( + SetCanUseNetworkResourcesForLiveStreamingWhilePausedRequest + request) => + _playerAudioHandler + .customSetCanUseNetworkResourcesForLiveStreamingWhilePaused( + request); + + @override + Future setPreferredPeakBitRate( + SetPreferredPeakBitRateRequest request) => + _playerAudioHandler.customSetPreferredPeakBitRate(request); } class _PlayerAudioHandler extends BaseAudioHandler @@ -430,6 +484,16 @@ class _PlayerAudioHandler extends BaseAudioHandler Future customSetVolume(SetVolumeRequest request) async => await (await _player).setVolume(request); + Future customSetSpeed(SetSpeedRequest request) async => + await (await _player).setSpeed(request); + + Future customSetPitch(SetPitchRequest request) async => + await (await _player).setPitch(request); + + Future customSetSkipSilence( + SetSkipSilenceRequest request) async => + await (await _player).setSkipSilence(request); + Future customPlayerSeek(SeekRequest request) async => await (await _player).seek(request); @@ -484,6 +548,39 @@ class _PlayerAudioHandler extends BaseAudioHandler await (await _player) .setAutomaticallyWaitsToMinimizeStalling(request); + Future customAndroidEqualizerBandSetGain( + AndroidEqualizerBandSetGainRequest request) async => + await (await _player).androidEqualizerBandSetGain(request); + + Future + customAndroidEqualizerGetParameters( + AndroidEqualizerGetParametersRequest request) async => + await (await _player).androidEqualizerGetParameters(request); + + Future + customAndroidLoudnessEnhancerSetTargetGain( + AndroidLoudnessEnhancerSetTargetGainRequest request) async => + await (await _player).androidLoudnessEnhancerSetTargetGain(request); + + Future customAudioEffectSetEnabled( + AudioEffectSetEnabledRequest request) async => + await (await _player).audioEffectSetEnabled(request); + + Future customSetAllowsExternalPlayback( + SetAllowsExternalPlaybackRequest request) async => + await (await _player).setAllowsExternalPlayback(request); + + Future + customSetCanUseNetworkResourcesForLiveStreamingWhilePaused( + SetCanUseNetworkResourcesForLiveStreamingWhilePausedRequest + request) async => + await (await _player) + .setCanUseNetworkResourcesForLiveStreamingWhilePaused(request); + + Future customSetPreferredPeakBitRate( + SetPreferredPeakBitRateRequest request) async => + await (await _player).setPreferredPeakBitRate(request); + void _updateQueue() { queue.add(sequence.map((source) => source.tag as MediaItem).toList()); } diff --git a/just_audio_background/pubspec.yaml b/just_audio_background/pubspec.yaml index 6f405a5db..9e3a969c3 100644 --- a/just_audio_background/pubspec.yaml +++ b/just_audio_background/pubspec.yaml @@ -1,7 +1,7 @@ name: just_audio_background description: An add-on for just_audio that supports background playback and media notifications. homepage: https://github.com/ryanheise/just_audio/tree/master/just_audio_background -version: 0.0.1-beta.10 +version: 0.0.1-beta.11 topics: - audio - sound @@ -9,7 +9,7 @@ topics: - background dependencies: - just_audio_platform_interface: ^4.2.1 + just_audio_platform_interface: ^4.2.2 # just_audio_platform_interface: # path: ../just_audio_platform_interface audio_service: ^0.18.9 @@ -25,5 +25,5 @@ dev_dependencies: flutter_lints: ^2.0.1 environment: - sdk: ">=2.14.0 <4.0.0" + sdk: ">=2.17.0 <4.0.0" flutter: ">=3.0.0" From 4cd629542cc2fa737da56cb59bf4ba465523a2a8 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Mon, 13 Nov 2023 12:43:51 +1100 Subject: [PATCH 25/64] Formatting --- just_audio/lib/just_audio.dart | 2 +- just_audio/test/just_audio_test.dart | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/just_audio/lib/just_audio.dart b/just_audio/lib/just_audio.dart index f06f85181..828df58a7 100644 --- a/just_audio/lib/just_audio.dart +++ b/just_audio/lib/just_audio.dart @@ -3280,7 +3280,7 @@ _ProxyHandler _proxyHandlerForUri( final requestHeaders = {}; request.headers .forEach((name, value) => requestHeaders[name] = value.join(', ')); - //write supplied headers last (to ensure supplied headers aren't overwritten) + // write supplied headers last (to ensure supplied headers aren't overwritten) headers?.forEach((name, value) => requestHeaders[name] = value); final originRequest = await _getUrl(client, redirectedUri ?? uri, headers: requestHeaders); diff --git a/just_audio/test/just_audio_test.dart b/just_audio/test/just_audio_test.dart index 91bf39ba2..6b67812d1 100644 --- a/just_audio/test/just_audio_test.dart +++ b/just_audio/test/just_audio_test.dart @@ -397,7 +397,10 @@ void runTests() { await server.start(); final player = AudioPlayer(); // This simulates an actual URL - var headers = {'custom-header': 'Hello', 'accept-encoding' : 'custom-encoding'}; + var headers = { + 'custom-header': 'Hello', + 'accept-encoding': 'custom-encoding', + }; final uri = Uri.parse( 'http://${InternetAddress.loopbackIPv4.address}:${server.port}/proxy/foo.mp3'); await player.setUrl('$uri', headers: headers); @@ -411,8 +414,10 @@ void runTests() { expect(responseText, equals('Hello')); expect(response.headers.value(HttpHeaders.contentTypeHeader), equals('audio/mock')); - //MockWebServer returns original request headers in response headers (with 'original_' prepended) - headers.forEach((key, value) => expect(response.headers.value('original_$key'),value)); + // MockWebServer returns original request headers in response headers + // (with 'original_' prepended) + headers.forEach( + (key, value) => expect(response.headers.value('original_$key'), value)); await server.stop(); await player.dispose(); }); @@ -1767,7 +1772,8 @@ class MockWebServer { response.contentLength = body.length; response.statusCode = HttpStatus.ok; response.headers.set(HttpHeaders.contentTypeHeader, 'audio/mock'); - request.headers.forEach((name, value) => response.headers.set('original_$name', value)); + request.headers.forEach( + (name, value) => response.headers.set('original_$name', value)); response.add(body); await response.flush(); await response.close(); From 34ee28928ec21f76290c935faf6b6296252d2c30 Mon Sep 17 00:00:00 2001 From: Yan Date: Mon, 13 Nov 2023 10:16:25 +0800 Subject: [PATCH 26/64] fix b/1121 --- just_audio_web/lib/just_audio_web.dart | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/just_audio_web/lib/just_audio_web.dart b/just_audio_web/lib/just_audio_web.dart index a98402a92..48d78ceae 100644 --- a/just_audio_web/lib/just_audio_web.dart +++ b/just_audio_web/lib/just_audio_web.dart @@ -961,10 +961,15 @@ class _PlayPauseQueue { Future _run() async { await for (var request in _queue.stream) { - if (request.playing) { - await audioElement.play(); - } else { - audioElement.pause(); + try { + if (request.playing) { + await audioElement.play(); + } else { + audioElement.pause(); + } + } catch (err) { + request.completer.completeError(err); + continue; } request.completer.complete(); } From 70eabd6b00c273a04bd02ea705ad6e92eda4687e Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Mon, 13 Nov 2023 18:25:10 +1100 Subject: [PATCH 27/64] Polish README. --- just_audio/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/just_audio/README.md b/just_audio/README.md index 1f3339cb9..00093c4d5 100644 --- a/just_audio/README.md +++ b/just_audio/README.md @@ -98,6 +98,7 @@ await playlist.removeAt(3); // Setting the HTTP user agent final player = AudioPlayer( userAgent: 'myradioapp/1.0 (Linux;Android 11) https://myradioapp.com', + useProxyForRequestHeaders: true, // default ); // Setting request headers @@ -105,7 +106,9 @@ final duration = await player.setUrl('https://foo.com/bar.mp3', headers: {'header1': 'value1', 'header2': 'value2'}); ``` -Note: By default, headers are implemented via a local HTTP proxy which on Android, iOS and macOS requires non-HTTPS support to be enabled. See [Platform Specific Configuration](#platform-specific-configuration). To use the platform's native implementation of headers instead of the proxy, pass `useProxyForRequestHeaders: false` into the `AudioPlayer` constructor. On iOS/macOS, this will use the undocumented `AVURLAssetHTTPHeaderFieldsKey` API which may present issues for App Store submission. +Note: By default, headers are implemented via a local HTTP proxy which on Android, iOS and macOS requires non-HTTPS support to be enabled. See [Platform Specific Configuration](#platform-specific-configuration). + +Alternatively, settings `useProxyForRequestHeaders: false` will use the platform's native headers implementation without a proxy. Although note that iOS doesn't offer an official native API for setting headers, and so this will use the undocumented `AVURLAssetHTTPHeaderFieldsKey` API (or in the case of the user-agent header on iOS 16 and above, the official `AVURLAssetHTTPUserAgentKey` API). ### Working with caches From ed742803f05ee382e043d1b634e0dd51e08f2ea7 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Mon, 13 Nov 2023 18:45:43 +1100 Subject: [PATCH 28/64] Clean up, version bump, CHANGELOG. --- just_audio/pubspec.yaml | 2 +- just_audio_web/CHANGELOG.md | 4 ++++ just_audio_web/lib/just_audio_web.dart | 7 +++---- just_audio_web/pubspec.yaml | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/just_audio/pubspec.yaml b/just_audio/pubspec.yaml index 5d41e1248..1371cef96 100644 --- a/just_audio/pubspec.yaml +++ b/just_audio/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: just_audio_platform_interface: ^4.2.2 # just_audio_platform_interface: # path: ../just_audio_platform_interface - just_audio_web: ^0.4.8 + just_audio_web: ^0.4.9 # just_audio_web: # path: ../just_audio_web audio_session: ^0.1.14 diff --git a/just_audio_web/CHANGELOG.md b/just_audio_web/CHANGELOG.md index 2573c6218..4038b0d12 100644 --- a/just_audio_web/CHANGELOG.md +++ b/just_audio_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.9 + +* Fix bug to ensure play exceptions pass through (@idy). + ## 0.4.8 * Update minimum flutter version to 3.0. diff --git a/just_audio_web/lib/just_audio_web.dart b/just_audio_web/lib/just_audio_web.dart index 48d78ceae..f55f16234 100644 --- a/just_audio_web/lib/just_audio_web.dart +++ b/just_audio_web/lib/just_audio_web.dart @@ -967,11 +967,10 @@ class _PlayPauseQueue { } else { audioElement.pause(); } - } catch (err) { - request.completer.completeError(err); - continue; + request.completer.complete(); + } catch (e, st) { + request.completer.completeError(e, st); } - request.completer.complete(); } } } diff --git a/just_audio_web/pubspec.yaml b/just_audio_web/pubspec.yaml index bb096d9b0..ea92024af 100644 --- a/just_audio_web/pubspec.yaml +++ b/just_audio_web/pubspec.yaml @@ -1,7 +1,7 @@ name: just_audio_web description: Web platform implementation of just_audio. This implementation is endorsed and therefore doesn't require a direct dependency. homepage: https://github.com/ryanheise/just_audio/tree/master/just_audio_web -version: 0.4.8 +version: 0.4.9 flutter: plugin: From 4a1e79988f9bc97392413cc3f717eba687df7afb Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Fri, 8 Dec 2023 14:35:58 +1100 Subject: [PATCH 29/64] Migrate to package:web --- just_audio/example/pubspec.yaml | 2 +- just_audio/pubspec.yaml | 6 +- just_audio_background/example/pubspec.yaml | 2 +- just_audio_background/pubspec.yaml | 4 +- just_audio_web/CHANGELOG.md | 4 + just_audio_web/lib/just_audio_web.dart | 91 +++++++++++++--------- just_audio_web/pubspec.yaml | 7 +- 7 files changed, 71 insertions(+), 45 deletions(-) diff --git a/just_audio/example/pubspec.yaml b/just_audio/example/pubspec.yaml index f187b4c71..e5d931ddf 100644 --- a/just_audio/example/pubspec.yaml +++ b/just_audio/example/pubspec.yaml @@ -3,7 +3,7 @@ description: Demonstrates how to use the just_audio plugin. publish_to: 'none' environment: - sdk: ">=2.17.0 <4.0.0" + sdk: ">=3.0.0 <4.0.0" dependencies: flutter: diff --git a/just_audio/pubspec.yaml b/just_audio/pubspec.yaml index 1371cef96..5e08f71d3 100644 --- a/just_audio/pubspec.yaml +++ b/just_audio/pubspec.yaml @@ -10,14 +10,14 @@ topics: - background environment: - sdk: ">=2.17.0 <4.0.0" - flutter: ">=3.0.0" + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" dependencies: just_audio_platform_interface: ^4.2.2 # just_audio_platform_interface: # path: ../just_audio_platform_interface - just_audio_web: ^0.4.9 + just_audio_web: ^0.4.10 # just_audio_web: # path: ../just_audio_web audio_session: ^0.1.14 diff --git a/just_audio_background/example/pubspec.yaml b/just_audio_background/example/pubspec.yaml index 1618cfb96..069bcf1ac 100644 --- a/just_audio_background/example/pubspec.yaml +++ b/just_audio_background/example/pubspec.yaml @@ -3,7 +3,7 @@ description: Demonstrates how to use the just_audio plugin. publish_to: 'none' environment: - sdk: ">=2.17.0 <4.0.0" + sdk: ">=3.0.0 <4.0.0" dependencies: flutter: diff --git a/just_audio_background/pubspec.yaml b/just_audio_background/pubspec.yaml index 9e3a969c3..6316c760c 100644 --- a/just_audio_background/pubspec.yaml +++ b/just_audio_background/pubspec.yaml @@ -25,5 +25,5 @@ dev_dependencies: flutter_lints: ^2.0.1 environment: - sdk: ">=2.17.0 <4.0.0" - flutter: ">=3.0.0" + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" diff --git a/just_audio_web/CHANGELOG.md b/just_audio_web/CHANGELOG.md index 4038b0d12..1cc9f5a80 100644 --- a/just_audio_web/CHANGELOG.md +++ b/just_audio_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.10 + +* Migrate to package:web. + ## 0.4.9 * Fix bug to ensure play exceptions pass through (@idy). diff --git a/just_audio_web/lib/just_audio_web.dart b/just_audio_web/lib/just_audio_web.dart index f55f16234..1f78be5f0 100644 --- a/just_audio_web/lib/just_audio_web.dart +++ b/just_audio_web/lib/just_audio_web.dart @@ -1,11 +1,12 @@ import 'dart:async'; -import 'dart:html'; +import 'dart:js_interop'; import 'dart:math'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:just_audio_platform_interface/just_audio_platform_interface.dart'; +import 'package:web/web.dart'; /// The web implementation of [JustAudioPlatform]. class JustAudioPlugin extends JustAudioPlatform { @@ -99,7 +100,9 @@ abstract class JustAudioPlayer extends AudioPlayerPlatform { /// An HTML5-specific implementation of [JustAudioPlayer]. class Html5AudioPlayer extends JustAudioPlayer { - final _audioElement = AudioElement(); + // Uncomment after: https://github.com/dart-lang/web/issues/124 + //final _audioElement = HTMLAudioElement(); + final _audioElement = document.createElement('audio') as HTMLAudioElement; late final _playPauseQueue = _PlayPauseQueue(_audioElement); Completer? _durationCompleter; AudioSourcePlayer? _audioSourcePlayer; @@ -109,36 +112,54 @@ class Html5AudioPlayer extends JustAudioPlayer { /// Creates an [Html5AudioPlayer] with the given [id]. Html5AudioPlayer({required String id}) : super(id: id) { - _audioElement.addEventListener('durationchange', (event) { - _durationCompleter?.complete(); - broadcastPlaybackEvent(); - }); - _audioElement.addEventListener('error', (event) { - _durationCompleter?.completeError(_audioElement.error!); - }); - _audioElement.addEventListener('ended', (event) async { - _currentAudioSourcePlayer?.complete(); - }); - _audioElement.addEventListener('timeupdate', (event) { - _currentAudioSourcePlayer - ?.timeUpdated(_audioElement.currentTime as double); - }); - _audioElement.addEventListener('loadstart', (event) { - transition(ProcessingStateMessage.buffering); - }); - _audioElement.addEventListener('waiting', (event) { - transition(ProcessingStateMessage.buffering); - }); - _audioElement.addEventListener('stalled', (event) { - transition(ProcessingStateMessage.buffering); - }); - _audioElement.addEventListener('canplaythrough', (event) { - _audioElement.playbackRate = _speed; - transition(ProcessingStateMessage.ready); - }); - _audioElement.addEventListener('progress', (event) { - broadcastPlaybackEvent(); - }); + _audioElement.addEventListener( + 'durationchange', + (event) { + _durationCompleter?.complete(); + broadcastPlaybackEvent(); + }.toJS); + _audioElement.addEventListener( + 'error', + (event) { + _durationCompleter?.completeError(_audioElement.error!); + }.toJS); + _audioElement.addEventListener( + 'ended', + (event) async { + _currentAudioSourcePlayer?.complete(); + }.toJS); + _audioElement.addEventListener( + 'timeupdate', + (event) { + _currentAudioSourcePlayer + ?.timeUpdated(_audioElement.currentTime.toDouble()); + }.toJS); + _audioElement.addEventListener( + 'loadstart', + (event) { + transition(ProcessingStateMessage.buffering); + }.toJS); + _audioElement.addEventListener( + 'waiting', + (event) { + transition(ProcessingStateMessage.buffering); + }.toJS); + _audioElement.addEventListener( + 'stalled', + (event) { + transition(ProcessingStateMessage.buffering); + }.toJS); + _audioElement.addEventListener( + 'canplaythrough', + (event) { + _audioElement.playbackRate = _speed; + transition(ProcessingStateMessage.ready); + }.toJS); + _audioElement.addEventListener( + 'progress', + (event) { + broadcastPlaybackEvent(); + }.toJS); } /// The current playback order, depending on whether shuffle mode is enabled. @@ -571,7 +592,7 @@ abstract class IndexedAudioSourcePlayer extends AudioSourcePlayer { Duration get bufferedPosition; /// The audio element that renders the audio. - AudioElement get _audioElement => html5AudioPlayer._audioElement; + HTMLAudioElement get _audioElement => html5AudioPlayer._audioElement; _PlayPauseQueue get _playPauseQueue => html5AudioPlayer._playPauseQueue; @@ -940,7 +961,7 @@ class _PlayPauseRequest { } class _PlayPauseQueue { - final AudioElement audioElement; + final HTMLAudioElement audioElement; final _queue = StreamController<_PlayPauseRequest>(); _PlayPauseQueue(this.audioElement) { @@ -963,7 +984,7 @@ class _PlayPauseQueue { await for (var request in _queue.stream) { try { if (request.playing) { - await audioElement.play(); + await audioElement.play().toDart; } else { audioElement.pause(); } diff --git a/just_audio_web/pubspec.yaml b/just_audio_web/pubspec.yaml index ea92024af..6d469d5b9 100644 --- a/just_audio_web/pubspec.yaml +++ b/just_audio_web/pubspec.yaml @@ -1,7 +1,7 @@ name: just_audio_web description: Web platform implementation of just_audio. This implementation is endorsed and therefore doesn't require a direct dependency. homepage: https://github.com/ryanheise/just_audio/tree/master/just_audio_web -version: 0.4.9 +version: 0.4.10 flutter: plugin: @@ -18,10 +18,11 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter + web: '>=0.3.0 <0.5.0' dev_dependencies: flutter_lints: ^2.0.1 environment: - sdk: ">=2.17.0 <4.0.0" - flutter: ">=3.0.0" + sdk: ^3.2.1 + flutter: ">=3.16.0" From a64b7e3067b1d869c98654d0c79655a4e04ae470 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Fri, 1 Mar 2024 02:19:04 +1100 Subject: [PATCH 30/64] Android setup instructions for SDK 34 --- just_audio_background/README.md | 2 ++ .../example/android/app/src/main/AndroidManifest.xml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/just_audio_background/README.md b/just_audio_background/README.md index 4561e0f27..b2cecc76d 100644 --- a/just_audio_background/README.md +++ b/just_audio_background/README.md @@ -59,6 +59,8 @@ Make the following changes to your project's `AndroidManifest.xml` file: + + diff --git a/just_audio_background/example/android/app/src/main/AndroidManifest.xml b/just_audio_background/example/android/app/src/main/AndroidManifest.xml index 88691099c..a4d660627 100644 --- a/just_audio_background/example/android/app/src/main/AndroidManifest.xml +++ b/just_audio_background/example/android/app/src/main/AndroidManifest.xml @@ -6,6 +6,8 @@ + + - +