Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/minor'
Browse files Browse the repository at this point in the history
  • Loading branch information
bdlukaa committed Sep 16, 2024
2 parents 26f8be1 + b5fad79 commit 1ada994
Show file tree
Hide file tree
Showing 56 changed files with 957 additions and 285 deletions.
34 changes: 32 additions & 2 deletions just_audio/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,36 @@
## 0.10.1
## 0.9.40

*
* Fix JDK 21 compile error.

## 0.9.39

* Apply preferPreciseDurationAndTiming to files (@canxin121).
* Add tag parameter to setUrl/setFilePath/setAsset (@mathisfouques).
* Add tag parameter to setClip (@goviral-ma).
* Support rxdart 0.28.x.

## 0.9.38

* Migrate to package:web.
* Add AudioPlayer.setWebCrossOrigin for CORS on web (@danielwinkler).

## 0.9.37

* Support useLazyPreparation on iOS/macOS.
* Add index in sequence to errors for Android/iOS/macOS.
* Fix seek to index UI update on iOS/macOS.

## 0.9.36

* Add setAllowsExternalPlayback on iOS/macOS.
* Support index-based seeking on Android/iOS/macOS.
* Add option to send headers/userAgent without proxy.
* Fix bug where user supplied headers are overwritten by defaults (@ctedgar).

## 0.9.35

* Fix nullable completer argument type (@srawlins).
* Support uuid 4.0.0 (@Pante).

## 0.9.34

Expand Down
15 changes: 12 additions & 3 deletions just_audio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,17 @@ 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
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).
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

Expand Down Expand Up @@ -180,15 +183,18 @@ try {
// Catching errors during playback (e.g. lost network connection)
player.playbackEventStream.listen((event) {}, onError: (Object e, StackTrace st) {
if (e is PlayerException) {
if (e is PlatformException) {
print('Error code: ${e.code}');
print('Error message: ${e.message}');
print('AudioSource index: ${e.details?["index"]}');
} else {
print('An error occurred: $e');
}
});
```

Note: In a future release, the exception type on `playbackEventStream` will change from `PlatformException` to `PlayerException`.

### Working with state streams

See [The state model](#the-state-model) for details.
Expand Down Expand Up @@ -430,7 +436,7 @@ Please also consider pressing the thumbs up button at the top of [this page](htt
| read from file | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| read from asset | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| read from byte stream | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| request headers | ✅ | ✅ | ✅ | | ✅ | ✅ |
| request headers | ✅ | ✅ | ✅ | * | ✅ | ✅ |
| DASH | ✅ | | | | ✅ | ✅ |
| HLS | ✅ | ✅ | ✅ | | ✅ | ✅ |
| ICY metadata | ✅ | ✅ | ✅ | | | |
Expand All @@ -450,6 +456,9 @@ Please also consider pressing the thumbs up button at the top of [this page](htt
| equalizer | ✅ | | | | | ✅ |
| volume boost | ✅ | | | | | ✅ |

(*): While request headers cannot be set directly on Web, cookies can be used to send information in the [Cookie header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie). See also `AudioPlayer.setWebCrossOrigin` to allow sending cookies when loading audio files from the same origin or a different origin.


## Experimental features

| Feature | Android | iOS | macOS | Web |
Expand Down
21 changes: 11 additions & 10 deletions just_audio/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
group 'com.ryanheise.just_audio'
version '1.0'
def args = ["-Xlint:deprecation","-Xlint:unchecked","-Werror"]
def args = ["-Xlint:deprecation","-Xlint:unchecked"]

buildscript {
repositories {
Expand Down Expand Up @@ -31,10 +31,10 @@ android {
if (project.android.hasProperty("namespace")) {
namespace 'com.ryanheise.just_audio'
}
compileSdkVersion 33
compileSdk 34

defaultConfig {
minSdkVersion 16
minSdk 16
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

Expand All @@ -46,12 +46,13 @@ android {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}

dependencies {
def exoplayer_version = "2.18.7"
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-hls:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-smoothstreaming:$exoplayer_version"
dependencies {
def exoplayer_version = "2.18.7"
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-hls:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-smoothstreaming:$exoplayer_version"
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -100,7 +98,6 @@ public class AudioPlayer implements MethodCallHandler, Player.Listener, Metadata
private Map<String, Object> pendingPlaybackEvent;

private ExoPlayer player;
private DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
private Integer audioSessionId;
private MediaSource mediaSource;
private Integer currentIndex;
Expand Down Expand Up @@ -134,7 +131,14 @@ public void run() {
}
};

public AudioPlayer(final Context applicationContext, final BinaryMessenger messenger, final String id, Map<?, ?> audioLoadConfiguration, List<Object> rawAudioEffects, Boolean offloadSchedulingEnabled) {
public AudioPlayer(
final Context applicationContext,
final BinaryMessenger messenger,
final String id,
Map<?, ?> audioLoadConfiguration,
List<Object> rawAudioEffects,
Boolean offloadSchedulingEnabled
) {
this.context = applicationContext;
this.rawAudioEffects = rawAudioEffects;
this.offloadSchedulingEnabled = offloadSchedulingEnabled != null ? offloadSchedulingEnabled : false;
Expand All @@ -143,7 +147,6 @@ public AudioPlayer(final Context applicationContext, final BinaryMessenger messe
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) {
Expand Down Expand Up @@ -380,10 +383,10 @@ public void onPlayerError(PlaybackException error) {
Log.e(TAG, "default ExoPlaybackException: " + exoError.getUnexpectedException().getMessage());
}
// TODO: send both errorCode and type
sendError(String.valueOf(exoError.type), exoError.getMessage());
sendError(String.valueOf(exoError.type), exoError.getMessage(), mapOf("index", currentIndex));
} else {
Log.e(TAG, "default PlaybackException: " + error.getMessage());
sendError(String.valueOf(error.errorCode), error.getMessage());
sendError(String.valueOf(error.errorCode), error.getMessage(), mapOf("index", currentIndex));
}
errorCount++;
if (player.hasNextMediaItem() && currentIndex != null && errorCount <= 5) {
Expand Down Expand Up @@ -588,25 +591,44 @@ 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(), 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)
.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)
Expand Down Expand Up @@ -687,11 +709,24 @@ 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) {
final Map<String, String> stringHeaders = castToStringMap(headers);
String userAgent = null;
if (stringHeaders != null) {
userAgent = stringHeaders.remove("User-Agent");
if (userAgent == null) {
userAgent = stringHeaders.remove("user-agent");
}
}
if (userAgent == null) {
userAgent = Util.getUserAgent(context, "just_audio");
}
DefaultHttpDataSource.Factory httpDataSourceFactory = new DefaultHttpDataSource.Factory()
.setUserAgent(userAgent)
.setAllowCrossProtocolRedirects(true);
if (stringHeaders != null && stringHeaders.size() > 0) {
httpDataSourceFactory.setDefaultRequestProperties(stringHeaders);
}
return new DefaultDataSource.Factory(context, httpDataSourceFactory);
}

Expand Down Expand Up @@ -871,20 +906,24 @@ 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();
}
}

private void sendError(String errorCode, String errorMsg) {
sendError(errorCode, errorMsg, null);
}

private void sendError(String errorCode, String errorMsg, Object details) {
if (prepareResult != null) {
prepareResult.error(errorCode, errorMsg, null);
prepareResult.error(errorCode, errorMsg, details);
prepareResult = null;
}

eventChannel.error(errorCode, errorMsg, null);
eventChannel.error(errorCode, errorMsg, details);
}

private String getLowerCaseExtension(Uri uri) {
Expand Down Expand Up @@ -1036,6 +1075,15 @@ static Map<String, Object> mapOf(Object... args) {
return map;
}

static Map<String, String> castToStringMap(Map<?, ?> map) {
if (map == null) return null;
Map<String, String> map2 = new HashMap<>();
for (Object key : map.keySet()) {
map2.put((String)key, (String)map.get(key));
}
return map2;
}

enum ProcessingState {
none,
loading,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,17 @@ public void onMethodCall(MethodCall call, @NonNull Result result) {
break;
}
List<Object> 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")
)
);
result.success(null);
break;
}
Expand Down
Loading

0 comments on commit 1ada994

Please sign in to comment.