From 06d4da2b6ee155539847d978fbfd4620c5435a58 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Thu, 30 Sep 2021 23:48:33 +1000 Subject: [PATCH 1/5] Report URL from ICY metadata on iOS/macOS. --- just_audio/darwin/Classes/AudioPlayer.m | 16 +++++++--------- .../example/ios/Flutter/AppFrameworkInfo.plist | 2 +- just_audio/example/ios/Podfile.lock | 2 +- .../example/ios/Runner.xcodeproj/project.pbxproj | 6 +++--- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/just_audio/darwin/Classes/AudioPlayer.m b/just_audio/darwin/Classes/AudioPlayer.m index a3e077ff6..0c9ad3f4b 100644 --- a/just_audio/darwin/Classes/AudioPlayer.m +++ b/just_audio/darwin/Classes/AudioPlayer.m @@ -406,23 +406,20 @@ - (void)addItemObservers:(AVPlayerItem *)playerItem { } - (void)metadataOutput:(AVPlayerItemMetadataOutput *)output didOutputTimedMetadataGroups:(NSArray *)groups fromPlayerItemTrack:(AVPlayerItemTrack *)track { + // ICY headers aren't available here. Maybe do this in the proxy. BOOL hasIcyData = NO; NSString *title = (NSString *)[NSNull null]; + NSString *url = (NSString *)[NSNull null]; for (int i = 0; i < groups.count; i++) { - //NSLog(@"group %d", i); AVTimedMetadataGroup *group = groups[i]; for (int j = 0; j < group.items.count; j++) { - //NSLog(@"item %d", j); - AVMetadataItem *item = group.items[i]; - //NSLog(@"key: %@", item.key); - //NSLog(@"keySpace: %@", item.keySpace); - //NSLog(@"commonKey: %@", item.commonKey); - //NSLog(@"value: %@", item.value); - //NSLog(@"identifier: %@", item.identifier); - // TODO: Detect more metadata + AVMetadataItem *item = group.items[j]; if ([@"icy/StreamTitle" isEqualToString:item.identifier]) { hasIcyData = YES; title = (NSString *)item.value; + } else if ([@"icy/StreamUrl" isEqualToString:item.identifier]) { + hasIcyData = YES; + url = (NSString *)item.value; } } } @@ -430,6 +427,7 @@ - (void)metadataOutput:(AVPlayerItemMetadataOutput *)output didOutputTimedMetada _icyMetadata = @{ @"info": @{ @"title": title, + @"url": url, }, }; [self broadcastPlaybackEvent]; diff --git a/just_audio/example/ios/Flutter/AppFrameworkInfo.plist b/just_audio/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a..f2872cf47 100644 --- a/just_audio/example/ios/Flutter/AppFrameworkInfo.plist +++ b/just_audio/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 9.0 diff --git a/just_audio/example/ios/Podfile.lock b/just_audio/example/ios/Podfile.lock index 2e68f6798..9d1853c97 100644 --- a/just_audio/example/ios/Podfile.lock +++ b/just_audio/example/ios/Podfile.lock @@ -25,7 +25,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: audio_session: 4f3e461722055d21515cf3261b64c973c062f345 - Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c + Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c diff --git a/just_audio/example/ios/Runner.xcodeproj/project.pbxproj b/just_audio/example/ios/Runner.xcodeproj/project.pbxproj index 2984bbc40..f5fffe947 100644 --- a/just_audio/example/ios/Runner.xcodeproj/project.pbxproj +++ b/just_audio/example/ios/Runner.xcodeproj/project.pbxproj @@ -334,7 +334,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -413,7 +413,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -462,7 +462,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; From c8acc757ebe298231a69b7df1ffa11d441595284 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka <45696119+bdlukaa@users.noreply.github.com> Date: Thu, 7 Oct 2021 09:02:29 -0300 Subject: [PATCH 2/5] Bump just_audio_libwinmedia to 0.0.4 --- just_audio/README.md | 54 +++++++++++++++++---------------- just_audio/example/pubspec.yaml | 2 +- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/just_audio/README.md b/just_audio/README.md index c8793c363..6d35dd0f0 100644 --- a/just_audio/README.md +++ b/just_audio/README.md @@ -34,31 +34,31 @@ This project is supported by the amazing open source community of GitHub contrib ## Features -| Feature | Android | iOS | macOS | Web | Windows | -| ------- | :-------: | :-----: | :-----: | :-----: | :-----: | -| read from URL | ✅ | ✅ | ✅ | ✅ | ✅ | -| read from file | ✅ | ✅ | ✅ | ✅ | ✅ | -| read from asset | ✅ | ✅ | ✅ | ✅ | ✅ | -| read from byte stream | ✅ | ✅ | ✅ | ✅ | | -| request headers | ✅ | ✅ | ✅ | | | -| DASH | ✅ | | | | ✅ | -| HLS | ✅ | ✅ | ✅ | | ✅ | -| ICY metadata | ✅ | ✅ | ✅ | | | -| buffer status/position | ✅ | ✅ | ✅ | ✅ | ✅ | -| play/pause/seek | ✅ | ✅ | ✅ | ✅ | ✅ | -| set volume/speed | ✅ | ✅ | ✅ | ✅ | ✅ | -| clip audio | ✅ | ✅ | ✅ | ✅ | | -| playlists | ✅ | ✅ | ✅ | ✅ | ✅ | -| looping/shuffling | ✅ | ✅ | ✅ | ✅ | ✅ | -| compose audio | ✅ | ✅ | ✅ | ✅ | | -| gapless playback | ✅ | ✅ | ✅ | | ✅ | -| report player errors | ✅ | ✅ | ✅ | ✅ | | -| handle phonecall interruptions | ✅ | ✅ | | | | -| buffering/loading options | ✅ | ✅ | ✅ | | | -| set pitch | ✅ | | | | | -| skip silence | ✅ | | | | | -| equalizer | ✅ | | | | | -| volume boost | ✅ | | | | | +| Feature | Android | iOS | macOS | Web | Windows | Linux | +| ------------------------------ | :-----: | :-: | :---: | :-: | :-----: | :---: | +| read from URL | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| read from file | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| read from asset | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| read from byte stream | ✅ | ✅ | ✅ | ✅ | | | +| request headers | ✅ | ✅ | ✅ | | | | +| DASH | ✅ | | | | ✅ | | +| HLS | ✅ | ✅ | ✅ | | ✅ | | +| ICY metadata | ✅ | ✅ | ✅ | | | | +| buffer status/position | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| play/pause/seek | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| set volume/speed | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| clip audio | ✅ | ✅ | ✅ | ✅ | | +| playlists | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| looping/shuffling | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| compose audio | ✅ | ✅ | ✅ | ✅ | | +| gapless playback | ✅ | ✅ | ✅ | | ✅ | | +| report player errors | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| handle phonecall interruptions | ✅ | ✅ | | | | | +| buffering/loading options | ✅ | ✅ | ✅ | | | | +| set pitch | ✅ | | | | | | +| skip silence | ✅ | | | | | | +| equalizer | ✅ | | | | | | +| volume boost | ✅ | | | | | | ## Experimental features @@ -219,10 +219,12 @@ try { // iOS/macOS: maps to NSError.code // Android: maps to ExoPlayerException.type // Web: maps to MediaError.code + // Linux/Windows: maps to PlayerErrorCode.index print("Error code: ${e.code}"); // iOS/macOS: maps to NSError.localizedDescription // Android: maps to ExoPlaybackException.getMessage() - // Web: a generic message + // Web/Linux: a generic message + // Windows: MediaPlayerError.message print("Error message: ${e.message}"); } on PlayerInterruptedException catch (e) { // This call was interrupted since another audio source was loaded or the diff --git a/just_audio/example/pubspec.yaml b/just_audio/example/pubspec.yaml index 5306924ef..2602e9b10 100644 --- a/just_audio/example/pubspec.yaml +++ b/just_audio/example/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: sdk: flutter audio_session: ^0.1.5 rxdart: '^0.27.0' - just_audio_libwinmedia: ^0.0.3 + just_audio_libwinmedia: ^0.0.4 # just_audio_libwinmedia: # path: ../../../just_audio_libwinmedia just_audio: From ab9e4130172d74f48c73694cae1772351f769994 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Sun, 10 Oct 2021 00:32:00 +1100 Subject: [PATCH 3/5] Fix MIME type cache. --- 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 694924fcc..fa251bc67 100644 --- a/just_audio/lib/just_audio.dart +++ b/just_audio/lib/just_audio.dart @@ -2693,7 +2693,6 @@ class LockCachingAudioSource extends StreamAudioSource { _downloading = true; final cacheFile = await this.cacheFile; final partialCacheFile = await _partialCacheFile; - final mimeType = await _readCachedMimeType(); File getEffectiveCacheFile() => partialCacheFile.existsSync() ? partialCacheFile : cacheFile; @@ -2714,6 +2713,9 @@ class LockCachingAudioSource extends StreamAudioSource { // ignore: close_sinks final sink = (await _partialCacheFile).openWrite(); var sourceLength = response.contentLength; + final mimeType = response.headers.contentType.toString(); + final mimeFile = await _mimeFile; + await mimeFile.writeAsString(mimeType); final inProgressResponses = <_InProgressCacheResponse>[]; late StreamSubscription subscription; var percentProgress = 0; From 3554d9d327108eb5072b10ba709c1dfe208a6bb2 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Sun, 10 Oct 2021 00:04:46 +1100 Subject: [PATCH 4/5] Add radio example. --- just_audio/example/lib/example_radio.dart | 164 ++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 just_audio/example/lib/example_radio.dart diff --git a/just_audio/example/lib/example_radio.dart b/just_audio/example/lib/example_radio.dart new file mode 100644 index 000000000..d28853d5e --- /dev/null +++ b/just_audio/example/lib/example_radio.dart @@ -0,0 +1,164 @@ +// This is a minimal example demonstrating live streaming. +// +// To run: +// +// flutter run -t lib/example_radio.dart + +import 'package:audio_session/audio_session.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:just_audio/just_audio.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatefulWidget { + @override + _MyAppState createState() => _MyAppState(); +} + +class _MyAppState extends State with WidgetsBindingObserver { + final _player = AudioPlayer(); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance?.addObserver(this); + SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( + statusBarColor: Colors.black, + )); + _init(); + } + + Future _init() async { + // Inform the operating system of our app's audio attributes etc. + // We pick a reasonable default for an app that plays speech. + final session = await AudioSession.instance; + await session.configure(AudioSessionConfiguration.speech()); + // Listen to errors during playback. + _player.playbackEventStream.listen((event) {}, + onError: (Object e, StackTrace stackTrace) { + print('A stream error occurred: $e'); + }); + // Try to load audio from a source and catch any errors. + try { + await _player.setAudioSource(AudioSource.uri( + Uri.parse("https://stream-uk1.radioparadise.com/aac-320"))); + } catch (e) { + print("Error loading audio source: $e"); + } + } + + @override + void dispose() { + WidgetsBinding.instance?.removeObserver(this); + // Release decoders and buffers back to the operating system making them + // available for other apps to use. + _player.dispose(); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.paused) { + // Release the player's resources when not in use. We use "stop" so that + // if the app resumes later, it will still remember what position to + // resume from. + _player.stop(); + } + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + home: Scaffold( + body: SafeArea( + child: Container( + width: double.maxFinite, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + StreamBuilder( + stream: _player.icyMetadataStream, + builder: (context, snapshot) { + final metadata = snapshot.data; + final title = metadata?.info?.title ?? ''; + final url = metadata?.info?.url; + return Column( + children: [ + if (url != null) Image.network(url), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text(title, + style: Theme.of(context).textTheme.headline6), + ), + ], + ); + }, + ), + // Display play/pause button and volume/speed sliders. + ControlButtons(_player), + ], + ), + ), + ), + ), + ); + } +} + +/// Displays the play/pause button and volume/speed sliders. +class ControlButtons extends StatelessWidget { + final AudioPlayer player; + + ControlButtons(this.player); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + /// This StreamBuilder rebuilds whenever the player state changes, which + /// includes the playing/paused state and also the + /// loading/buffering/ready state. Depending on the state we show the + /// appropriate button or loading indicator. + StreamBuilder( + stream: player.playerStateStream, + builder: (context, snapshot) { + final playerState = snapshot.data; + final processingState = playerState?.processingState; + final playing = playerState?.playing; + if (processingState == ProcessingState.loading || + processingState == ProcessingState.buffering) { + return Container( + margin: EdgeInsets.all(8.0), + width: 64.0, + height: 64.0, + child: CircularProgressIndicator(), + ); + } else if (playing != true) { + return IconButton( + icon: Icon(Icons.play_arrow), + iconSize: 64.0, + onPressed: player.play, + ); + } else if (processingState != ProcessingState.completed) { + return IconButton( + icon: Icon(Icons.pause), + iconSize: 64.0, + onPressed: player.pause, + ); + } else { + return IconButton( + icon: Icon(Icons.replay), + iconSize: 64.0, + onPressed: () => player.seek(Duration.zero), + ); + } + }, + ), + ], + ); + } +} From 5de01ec039672010c376c6d1a16c34fc0b33c659 Mon Sep 17 00:00:00 2001 From: Ryan Heise Date: Sun, 10 Oct 2021 17:53:53 +1100 Subject: [PATCH 5/5] just_audio 0.9.13 --- just_audio/CHANGELOG.md | 7 +++++++ just_audio/README.md | 46 ++++++++++++++++++++--------------------- just_audio/pubspec.yaml | 2 +- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/just_audio/CHANGELOG.md b/just_audio/CHANGELOG.md index 46ce16dff..1f5fedf06 100644 --- a/just_audio/CHANGELOG.md +++ b/just_audio/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.9.13 + +* Fix MIME type in LockCachingAudioSource. +* Add radio/livestream example. +* Report URL from ICY metadata on iOS/macOS. +* Update example to just_audio_libwinmedia 0.0.4 (@bdlukaa) + ## 0.9.12 * Windows/Linux support (@bdlukaa, credit to @alexmercerind for libwinmedia). diff --git a/just_audio/README.md b/just_audio/README.md index 6d35dd0f0..b3428f52c 100644 --- a/just_audio/README.md +++ b/just_audio/README.md @@ -36,29 +36,29 @@ This project is supported by the amazing open source community of GitHub contrib | Feature | Android | iOS | macOS | Web | Windows | Linux | | ------------------------------ | :-----: | :-: | :---: | :-: | :-----: | :---: | -| read from URL | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| read from file | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| read from asset | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| read from byte stream | ✅ | ✅ | ✅ | ✅ | | | -| request headers | ✅ | ✅ | ✅ | | | | -| DASH | ✅ | | | | ✅ | | -| HLS | ✅ | ✅ | ✅ | | ✅ | | -| ICY metadata | ✅ | ✅ | ✅ | | | | -| buffer status/position | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| play/pause/seek | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| set volume/speed | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| clip audio | ✅ | ✅ | ✅ | ✅ | | -| playlists | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| looping/shuffling | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| compose audio | ✅ | ✅ | ✅ | ✅ | | -| gapless playback | ✅ | ✅ | ✅ | | ✅ | | -| report player errors | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| handle phonecall interruptions | ✅ | ✅ | | | | | -| buffering/loading options | ✅ | ✅ | ✅ | | | | -| set pitch | ✅ | | | | | | -| skip silence | ✅ | | | | | | -| equalizer | ✅ | | | | | | -| volume boost | ✅ | | | | | | +| read from URL | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| read from file | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| read from asset | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| read from byte stream | ✅ | ✅ | ✅ | ✅ | | | +| request headers | ✅ | ✅ | ✅ | | | | +| DASH | ✅ | | | | ✅ | | +| HLS | ✅ | ✅ | ✅ | | ✅ | | +| ICY metadata | ✅ | ✅ | ✅ | | | | +| buffer status/position | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| play/pause/seek | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| set volume/speed | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| clip audio | ✅ | ✅ | ✅ | ✅ | | | +| playlists | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| looping/shuffling | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| compose audio | ✅ | ✅ | ✅ | ✅ | | | +| gapless playback | ✅ | ✅ | ✅ | | ✅ | | +| report player errors | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| handle phonecall interruptions | ✅ | ✅ | | | | | +| buffering/loading options | ✅ | ✅ | ✅ | | | | +| set pitch | ✅ | | | | | | +| skip silence | ✅ | | | | | | +| equalizer | ✅ | | | | | | +| volume boost | ✅ | | | | | | ## Experimental features diff --git a/just_audio/pubspec.yaml b/just_audio/pubspec.yaml index 534bc7fed..5d98a7fa3 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.12 +version: 0.9.13 homepage: https://github.com/ryanheise/just_audio/tree/master/just_audio environment: