Add setAndroidAudioAttributes and androidAudioSessionIdStream.

This commit is contained in:
Ryan Heise 2020-08-14 14:38:55 +10:00
parent ed9a5ffdce
commit 64d55345f8
4 changed files with 111 additions and 1 deletions

View File

@ -9,6 +9,8 @@ import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.audio.AudioListener;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataOutput;
import com.google.android.exoplayer2.metadata.icy.IcyHeaders;
@ -46,7 +48,7 @@ import java.util.List;
import java.util.Map;
import java.util.Random;
public class AudioPlayer implements MethodCallHandler, Player.EventListener, MetadataOutput {
public class AudioPlayer implements MethodCallHandler, Player.EventListener, AudioListener, MetadataOutput {
static final String TAG = "AudioPlayer";
@ -76,6 +78,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
private int errorCount;
private SimpleExoPlayer player;
private Integer audioSessionId;
private MediaSource mediaSource;
private Integer currentIndex;
private Map<LoopingMediaSource, MediaSource> loopingChildren = new HashMap<>();
@ -136,6 +139,15 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
handler.post(bufferWatcher);
}
@Override
public void onAudioSessionId(int audioSessionId) {
if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) {
this.audioSessionId = null;
} else {
this.audioSessionId = audioSessionId;
}
}
@Override
public void onMetadata(Metadata metadata) {
for (int i = 0; i < metadata.length(); i++) {
@ -347,6 +359,10 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
case "concatenating.clear":
concatenating(args.get(0)).clear(handler, () -> result.success(null));
break;
case "setAndroidAudioAttributes":
setAudioAttributes((Map<?, ?>)args.get(0));
result.success(null);
break;
default:
result.notImplemented();
break;
@ -532,9 +548,19 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
player = new SimpleExoPlayer.Builder(context).build();
player.addMetadataOutput(this);
player.addListener(this);
player.addAudioListener(this);
}
}
private void setAudioAttributes(Map<?, ?> json) {
AudioAttributes.Builder builder = new AudioAttributes.Builder();
builder.setContentType((Integer)json.get("contentType"));
builder.setFlags((Integer)json.get("flags"));
builder.setUsage((Integer)json.get("usage"));
builder.setAllowedCapturePolicy((Integer)json.get("allowedCapturePolicy"));
player.setAudioAttributes(builder.build());
}
private void broadcastPlaybackEvent() {
final Map<String, Object> event = new HashMap<String, Object>();
event.put("processingState", processingState.ordinal());
@ -544,6 +570,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
event.put("icyMetadata", collectIcyMetadata());
event.put("duration", duration = getDuration());
event.put("currentIndex", currentIndex);
event.put("androidAudioSessionId", audioSessionId);
if (eventSink != null) {
eventSink.success(event);

View File

@ -138,6 +138,8 @@
} else if ([@"concatenating.clear" isEqualToString:call.method]) {
[self concatenatingClear:(NSString*)args[0]];
result(nil);
} else if ([@"setAndroidAudioAttributes" isEqualToString:call.method]) {
result(nil);
} else {
result(FlutterMethodNotImplemented);
}

View File

@ -82,6 +82,7 @@ class AudioPlayer {
final _sequenceStateSubject = BehaviorSubject<SequenceState>();
final _loopModeSubject = BehaviorSubject<LoopMode>();
final _shuffleModeEnabledSubject = BehaviorSubject<bool>();
final _androidAudioSessionIdSubject = BehaviorSubject<int>();
BehaviorSubject<Duration> _positionSubject;
bool _automaticallyWaitsToMinimizeStalling = true;
@ -97,6 +98,7 @@ class AudioPlayer {
duration: null,
icyMetadata: null,
currentIndex: null,
androidAudioSessionId: null,
);
_playbackEventSubject.add(_playbackEvent);
_eventChannelStream = EventChannel('com.ryanheise.just_audio.events.$_id')
@ -121,6 +123,7 @@ class AudioPlayer {
? null
: IcyMetadata.fromJson(data['icyMetadata']),
currentIndex: data['currentIndex'],
androidAudioSessionId: data['androidAudioSessionId'],
);
//print("created event object with state: ${_playbackEvent.state}");
return _playbackEvent;
@ -146,6 +149,10 @@ class AudioPlayer {
.map((event) => event.currentIndex)
.distinct()
.handleError((err, stack) {/* noop */}));
_androidAudioSessionIdSubject.addStream(playbackEventStream
.map((event) => event.androidAudioSessionId)
.distinct()
.handleError((err, stack) {/* noop */}));
_sequenceStateSubject.addStream(
Rx.combineLatest2<List<IndexedAudioSource>, int, SequenceState>(
sequenceStream,
@ -274,6 +281,13 @@ class AudioPlayer {
Stream<bool> get shuffleModeEnabledStream =>
_shuffleModeEnabledSubject.stream;
/// The current Android AudioSession ID or `null` if not set.
int get androidAudioSessionId => _playbackEvent.androidAudioSessionId;
/// Broadcasts the current Android AudioSession ID or `null` if not set.
Stream<int> get androidAudioSessionIdStream =>
_androidAudioSessionIdSubject.stream;
/// Whether the player should automatically delay playback in order to
/// minimize stalling. (iOS 10.0 or later only)
bool get automaticallyWaitsToMinimizeStalling =>
@ -591,6 +605,14 @@ class AudioPlayer {
}
}
/// Set the Android audio attributes for this player. Has no effect on other
/// platforms. This will cause a new Android AudioSession ID to be generated.
Future<void> setAndroidAudioAttributes(
AndroidAudioAttributes audioAttributes) async {
await _invokeMethod(
'setAndroidAudioAttributes', [audioAttributes.toJson()]);
}
/// Release all resources associated with this player. You must invoke this
/// after you are done with the player.
Future<void> dispose() async {
@ -670,6 +692,9 @@ class PlaybackEvent {
/// The index of the currently playing item.
final int currentIndex;
/// The current Android AudioSession ID.
final int androidAudioSessionId;
PlaybackEvent({
@required this.processingState,
@required this.updateTime,
@ -678,6 +703,7 @@ class PlaybackEvent {
@required this.duration,
@required this.icyMetadata,
@required this.currentIndex,
@required this.androidAudioSessionId,
});
PlaybackEvent copyWith({
@ -689,6 +715,7 @@ class PlaybackEvent {
Duration duration,
IcyMetadata icyMetadata,
UriAudioSource currentIndex,
int androidAudioSessionId,
}) =>
PlaybackEvent(
processingState: processingState ?? this.processingState,
@ -698,6 +725,8 @@ class PlaybackEvent {
duration: duration ?? this.duration,
icyMetadata: icyMetadata ?? this.icyMetadata,
currentIndex: currentIndex ?? this.currentIndex,
androidAudioSessionId:
androidAudioSessionId ?? this.androidAudioSessionId,
);
@override
@ -853,6 +882,56 @@ enum IosCategory {
multiRoute,
}
/// The Android AudioAttributes to use with a player.
class AndroidAudioAttributes {
static const FLAG_AUDIBILITY_ENFORCED = 0x1 << 0;
final AndroidAudioContentType contentType;
final int flags;
final AndroidAudioUsage usage;
final AndroidAudioAllowedCapturePolicy allowedCapturePolicy;
AndroidAudioAttributes({
this.contentType = AndroidAudioContentType.unknown,
this.flags = 0,
this.usage = AndroidAudioUsage.unknown,
this.allowedCapturePolicy = AndroidAudioAllowedCapturePolicy.all,
});
Map toJson() => {
'contentType': contentType.index,
'flags': flags,
'usage': usage.index,
// The Android constant values for this enum are 1-indexed
'allowedCapturePolicy': allowedCapturePolicy.index + 1,
};
}
/// The content type options for [AndroidAudioAttributes].
enum AndroidAudioContentType { unknown, speech, music, movie, sonification }
/// The usage options for [AndroidAudioAttributes].
enum AndroidAudioUsage {
unknown,
media,
voiceCommunication,
voiceCommunicationSignalling,
alarm,
notification,
notificationRingtone,
notificationCommunicationRequest,
notificationCommunicationInstant,
notificationCommunicationDelayed,
notificationEvent,
assistanceAccessibility,
assistanceNavigationGuidance,
assistanceSonification,
unused_1,
assistant,
}
/// The allowed capture policy options for [AndroidAudioAttributes].
enum AndroidAudioAllowedCapturePolicy { all, system, none }
/// A local proxy HTTP server for making remote GET requests with headers.
///
/// TODO: Recursively attach headers to items in playlists like m3u8.

View File

@ -96,6 +96,8 @@ abstract class JustAudioPlayer {
return await concatenatingMove(args[0], args[1], args[2]);
case "concatenating.clear":
return await concatenatingClear(args[0]);
case "setAndroidAudioAttributes":
return null;
default:
throw PlatformException(code: 'Unimplemented');
}