Add setAndroidAudioAttributes and androidAudioSessionIdStream.
This commit is contained in:
parent
ed9a5ffdce
commit
64d55345f8
|
@ -9,6 +9,8 @@ import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
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.Metadata;
|
||||||
import com.google.android.exoplayer2.metadata.MetadataOutput;
|
import com.google.android.exoplayer2.metadata.MetadataOutput;
|
||||||
import com.google.android.exoplayer2.metadata.icy.IcyHeaders;
|
import com.google.android.exoplayer2.metadata.icy.IcyHeaders;
|
||||||
|
@ -46,7 +48,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
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";
|
static final String TAG = "AudioPlayer";
|
||||||
|
|
||||||
|
@ -76,6 +78,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
|
||||||
private int errorCount;
|
private int errorCount;
|
||||||
|
|
||||||
private SimpleExoPlayer player;
|
private SimpleExoPlayer player;
|
||||||
|
private Integer audioSessionId;
|
||||||
private MediaSource mediaSource;
|
private MediaSource mediaSource;
|
||||||
private Integer currentIndex;
|
private Integer currentIndex;
|
||||||
private Map<LoopingMediaSource, MediaSource> loopingChildren = new HashMap<>();
|
private Map<LoopingMediaSource, MediaSource> loopingChildren = new HashMap<>();
|
||||||
|
@ -136,6 +139,15 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
|
||||||
handler.post(bufferWatcher);
|
handler.post(bufferWatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAudioSessionId(int audioSessionId) {
|
||||||
|
if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) {
|
||||||
|
this.audioSessionId = null;
|
||||||
|
} else {
|
||||||
|
this.audioSessionId = audioSessionId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMetadata(Metadata metadata) {
|
public void onMetadata(Metadata metadata) {
|
||||||
for (int i = 0; i < metadata.length(); i++) {
|
for (int i = 0; i < metadata.length(); i++) {
|
||||||
|
@ -347,6 +359,10 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
|
||||||
case "concatenating.clear":
|
case "concatenating.clear":
|
||||||
concatenating(args.get(0)).clear(handler, () -> result.success(null));
|
concatenating(args.get(0)).clear(handler, () -> result.success(null));
|
||||||
break;
|
break;
|
||||||
|
case "setAndroidAudioAttributes":
|
||||||
|
setAudioAttributes((Map<?, ?>)args.get(0));
|
||||||
|
result.success(null);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
result.notImplemented();
|
result.notImplemented();
|
||||||
break;
|
break;
|
||||||
|
@ -532,9 +548,19 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
|
||||||
player = new SimpleExoPlayer.Builder(context).build();
|
player = new SimpleExoPlayer.Builder(context).build();
|
||||||
player.addMetadataOutput(this);
|
player.addMetadataOutput(this);
|
||||||
player.addListener(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() {
|
private void broadcastPlaybackEvent() {
|
||||||
final Map<String, Object> event = new HashMap<String, Object>();
|
final Map<String, Object> event = new HashMap<String, Object>();
|
||||||
event.put("processingState", processingState.ordinal());
|
event.put("processingState", processingState.ordinal());
|
||||||
|
@ -544,6 +570,7 @@ public class AudioPlayer implements MethodCallHandler, Player.EventListener, Met
|
||||||
event.put("icyMetadata", collectIcyMetadata());
|
event.put("icyMetadata", collectIcyMetadata());
|
||||||
event.put("duration", duration = getDuration());
|
event.put("duration", duration = getDuration());
|
||||||
event.put("currentIndex", currentIndex);
|
event.put("currentIndex", currentIndex);
|
||||||
|
event.put("androidAudioSessionId", audioSessionId);
|
||||||
|
|
||||||
if (eventSink != null) {
|
if (eventSink != null) {
|
||||||
eventSink.success(event);
|
eventSink.success(event);
|
||||||
|
|
|
@ -138,6 +138,8 @@
|
||||||
} else if ([@"concatenating.clear" isEqualToString:call.method]) {
|
} else if ([@"concatenating.clear" isEqualToString:call.method]) {
|
||||||
[self concatenatingClear:(NSString*)args[0]];
|
[self concatenatingClear:(NSString*)args[0]];
|
||||||
result(nil);
|
result(nil);
|
||||||
|
} else if ([@"setAndroidAudioAttributes" isEqualToString:call.method]) {
|
||||||
|
result(nil);
|
||||||
} else {
|
} else {
|
||||||
result(FlutterMethodNotImplemented);
|
result(FlutterMethodNotImplemented);
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,7 @@ class AudioPlayer {
|
||||||
final _sequenceStateSubject = BehaviorSubject<SequenceState>();
|
final _sequenceStateSubject = BehaviorSubject<SequenceState>();
|
||||||
final _loopModeSubject = BehaviorSubject<LoopMode>();
|
final _loopModeSubject = BehaviorSubject<LoopMode>();
|
||||||
final _shuffleModeEnabledSubject = BehaviorSubject<bool>();
|
final _shuffleModeEnabledSubject = BehaviorSubject<bool>();
|
||||||
|
final _androidAudioSessionIdSubject = BehaviorSubject<int>();
|
||||||
BehaviorSubject<Duration> _positionSubject;
|
BehaviorSubject<Duration> _positionSubject;
|
||||||
bool _automaticallyWaitsToMinimizeStalling = true;
|
bool _automaticallyWaitsToMinimizeStalling = true;
|
||||||
|
|
||||||
|
@ -97,6 +98,7 @@ class AudioPlayer {
|
||||||
duration: null,
|
duration: null,
|
||||||
icyMetadata: null,
|
icyMetadata: null,
|
||||||
currentIndex: null,
|
currentIndex: null,
|
||||||
|
androidAudioSessionId: null,
|
||||||
);
|
);
|
||||||
_playbackEventSubject.add(_playbackEvent);
|
_playbackEventSubject.add(_playbackEvent);
|
||||||
_eventChannelStream = EventChannel('com.ryanheise.just_audio.events.$_id')
|
_eventChannelStream = EventChannel('com.ryanheise.just_audio.events.$_id')
|
||||||
|
@ -121,6 +123,7 @@ class AudioPlayer {
|
||||||
? null
|
? null
|
||||||
: IcyMetadata.fromJson(data['icyMetadata']),
|
: IcyMetadata.fromJson(data['icyMetadata']),
|
||||||
currentIndex: data['currentIndex'],
|
currentIndex: data['currentIndex'],
|
||||||
|
androidAudioSessionId: data['androidAudioSessionId'],
|
||||||
);
|
);
|
||||||
//print("created event object with state: ${_playbackEvent.state}");
|
//print("created event object with state: ${_playbackEvent.state}");
|
||||||
return _playbackEvent;
|
return _playbackEvent;
|
||||||
|
@ -146,6 +149,10 @@ class AudioPlayer {
|
||||||
.map((event) => event.currentIndex)
|
.map((event) => event.currentIndex)
|
||||||
.distinct()
|
.distinct()
|
||||||
.handleError((err, stack) {/* noop */}));
|
.handleError((err, stack) {/* noop */}));
|
||||||
|
_androidAudioSessionIdSubject.addStream(playbackEventStream
|
||||||
|
.map((event) => event.androidAudioSessionId)
|
||||||
|
.distinct()
|
||||||
|
.handleError((err, stack) {/* noop */}));
|
||||||
_sequenceStateSubject.addStream(
|
_sequenceStateSubject.addStream(
|
||||||
Rx.combineLatest2<List<IndexedAudioSource>, int, SequenceState>(
|
Rx.combineLatest2<List<IndexedAudioSource>, int, SequenceState>(
|
||||||
sequenceStream,
|
sequenceStream,
|
||||||
|
@ -274,6 +281,13 @@ class AudioPlayer {
|
||||||
Stream<bool> get shuffleModeEnabledStream =>
|
Stream<bool> get shuffleModeEnabledStream =>
|
||||||
_shuffleModeEnabledSubject.stream;
|
_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
|
/// Whether the player should automatically delay playback in order to
|
||||||
/// minimize stalling. (iOS 10.0 or later only)
|
/// minimize stalling. (iOS 10.0 or later only)
|
||||||
bool get automaticallyWaitsToMinimizeStalling =>
|
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
|
/// Release all resources associated with this player. You must invoke this
|
||||||
/// after you are done with the player.
|
/// after you are done with the player.
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
|
@ -670,6 +692,9 @@ class PlaybackEvent {
|
||||||
/// The index of the currently playing item.
|
/// The index of the currently playing item.
|
||||||
final int currentIndex;
|
final int currentIndex;
|
||||||
|
|
||||||
|
/// The current Android AudioSession ID.
|
||||||
|
final int androidAudioSessionId;
|
||||||
|
|
||||||
PlaybackEvent({
|
PlaybackEvent({
|
||||||
@required this.processingState,
|
@required this.processingState,
|
||||||
@required this.updateTime,
|
@required this.updateTime,
|
||||||
|
@ -678,6 +703,7 @@ class PlaybackEvent {
|
||||||
@required this.duration,
|
@required this.duration,
|
||||||
@required this.icyMetadata,
|
@required this.icyMetadata,
|
||||||
@required this.currentIndex,
|
@required this.currentIndex,
|
||||||
|
@required this.androidAudioSessionId,
|
||||||
});
|
});
|
||||||
|
|
||||||
PlaybackEvent copyWith({
|
PlaybackEvent copyWith({
|
||||||
|
@ -689,6 +715,7 @@ class PlaybackEvent {
|
||||||
Duration duration,
|
Duration duration,
|
||||||
IcyMetadata icyMetadata,
|
IcyMetadata icyMetadata,
|
||||||
UriAudioSource currentIndex,
|
UriAudioSource currentIndex,
|
||||||
|
int androidAudioSessionId,
|
||||||
}) =>
|
}) =>
|
||||||
PlaybackEvent(
|
PlaybackEvent(
|
||||||
processingState: processingState ?? this.processingState,
|
processingState: processingState ?? this.processingState,
|
||||||
|
@ -698,6 +725,8 @@ class PlaybackEvent {
|
||||||
duration: duration ?? this.duration,
|
duration: duration ?? this.duration,
|
||||||
icyMetadata: icyMetadata ?? this.icyMetadata,
|
icyMetadata: icyMetadata ?? this.icyMetadata,
|
||||||
currentIndex: currentIndex ?? this.currentIndex,
|
currentIndex: currentIndex ?? this.currentIndex,
|
||||||
|
androidAudioSessionId:
|
||||||
|
androidAudioSessionId ?? this.androidAudioSessionId,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -853,6 +882,56 @@ enum IosCategory {
|
||||||
multiRoute,
|
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.
|
/// A local proxy HTTP server for making remote GET requests with headers.
|
||||||
///
|
///
|
||||||
/// TODO: Recursively attach headers to items in playlists like m3u8.
|
/// TODO: Recursively attach headers to items in playlists like m3u8.
|
||||||
|
|
|
@ -96,6 +96,8 @@ abstract class JustAudioPlayer {
|
||||||
return await concatenatingMove(args[0], args[1], args[2]);
|
return await concatenatingMove(args[0], args[1], args[2]);
|
||||||
case "concatenating.clear":
|
case "concatenating.clear":
|
||||||
return await concatenatingClear(args[0]);
|
return await concatenatingClear(args[0]);
|
||||||
|
case "setAndroidAudioAttributes":
|
||||||
|
return null;
|
||||||
default:
|
default:
|
||||||
throw PlatformException(code: 'Unimplemented');
|
throw PlatformException(code: 'Unimplemented');
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue