Skip to content

Commit

Permalink
Merge branch 'fix/ios_livestream'
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanheise committed Oct 10, 2021
2 parents d10ed4b + 5de01ec commit 2a22b69
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 43 deletions.
7 changes: 7 additions & 0 deletions just_audio/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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).
Expand Down
54 changes: 28 additions & 26 deletions just_audio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
16 changes: 7 additions & 9 deletions just_audio/darwin/Classes/AudioPlayer.m
Original file line number Diff line number Diff line change
Expand Up @@ -406,30 +406,28 @@ - (void)addItemObservers:(AVPlayerItem *)playerItem {
}

- (void)metadataOutput:(AVPlayerItemMetadataOutput *)output didOutputTimedMetadataGroups:(NSArray<AVTimedMetadataGroup *> *)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;
}
}
}
if (hasIcyData) {
_icyMetadata = @{
@"info": @{
@"title": title,
@"url": url,
},
};
[self broadcastPlaybackEvent];
Expand Down
2 changes: 1 addition & 1 deletion just_audio/example/ios/Flutter/AppFrameworkInfo.plist
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
<string>9.0</string>
</dict>
</plist>
2 changes: 1 addition & 1 deletion just_audio/example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
audio_session: 4f3e461722055d21515cf3261b64c973c062f345
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c

Expand Down
6 changes: 3 additions & 3 deletions just_audio/example/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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";
Expand Down
164 changes: 164 additions & 0 deletions just_audio/example/lib/example_radio.dart
Original file line number Diff line number Diff line change
@@ -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<MyApp> with WidgetsBindingObserver {
final _player = AudioPlayer();

@override
void initState() {
super.initState();
WidgetsBinding.instance?.addObserver(this);
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: Colors.black,
));
_init();
}

Future<void> _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<IcyMetadata?>(
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<PlayerState>(
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),
);
}
},
),
],
);
}
}
2 changes: 1 addition & 1 deletion just_audio/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 3 additions & 1 deletion just_audio/lib/just_audio.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion just_audio/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down

0 comments on commit 2a22b69

Please sign in to comment.