Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/video_player/video_player_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.9.0

* Implements `getAudioTracks()` and `selectAudioTrack()` methods for Android using ExoPlayer.

## 2.8.17

* Moves video event processing logic to Dart, and fixes an issue where buffer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
package io.flutter.plugins.videoplayer;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player;
import androidx.media3.common.Tracks;
import androidx.media3.exoplayer.ExoPlayer;

public abstract class ExoPlayerEventListener implements Player.Listener {
Expand Down Expand Up @@ -88,4 +91,34 @@ public void onPlayerError(@NonNull final PlaybackException error) {
public void onIsPlayingChanged(boolean isPlaying) {
events.onIsPlayingStateUpdate(isPlaying);
}

@Override
public void onTracksChanged(@NonNull Tracks tracks) {
// Find the currently selected audio track and notify
String selectedTrackId = findSelectedAudioTrackId(tracks);
events.onAudioTrackChanged(selectedTrackId);
}

/**
* Finds the ID of the currently selected audio track.
*
* @param tracks The current tracks
* @return The track ID in format "groupIndex_trackIndex", or null if no audio track is selected
*/
@Nullable
private String findSelectedAudioTrackId(@NonNull Tracks tracks) {
int groupIndex = 0;
for (Tracks.Group group : tracks.getGroups()) {
if (group.getType() == C.TRACK_TYPE_AUDIO && group.isSelected()) {
// Find the selected track within this group
for (int i = 0; i < group.length; i++) {
if (group.isTrackSelected(i)) {
return groupIndex + "_" + i;
}
}
}
groupIndex++;
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,23 @@
import static androidx.media3.common.Player.REPEAT_MODE_ALL;
import static androidx.media3.common.Player.REPEAT_MODE_OFF;

import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MediaItem;
import androidx.media3.common.PlaybackParameters;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackSelectionOverride;
import androidx.media3.common.Tracks;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
import io.flutter.view.TextureRegistry.SurfaceProducer;
import java.util.ArrayList;
import java.util.List;

/**
* A class responsible for managing video playback using {@link ExoPlayer}.
Expand All @@ -26,6 +35,7 @@ public abstract class VideoPlayer implements VideoPlayerInstanceApi {
@Nullable protected final SurfaceProducer surfaceProducer;
@Nullable private DisposeHandler disposeHandler;
@NonNull protected ExoPlayer exoPlayer;
@UnstableApi @Nullable protected DefaultTrackSelector trackSelector;

/** A closure-compatible signature since {@link java.util.function.Supplier} is API level 24. */
public interface ExoPlayerProvider {
Expand All @@ -43,6 +53,7 @@ public interface DisposeHandler {
void onDispose();
}

@UnstableApi
public VideoPlayer(
@NonNull VideoPlayerCallbacks events,
@NonNull MediaItem mediaItem,
Expand All @@ -52,6 +63,12 @@ public VideoPlayer(
this.videoPlayerEvents = events;
this.surfaceProducer = surfaceProducer;
exoPlayer = exoPlayerProvider.get();

// Try to get the track selector from the ExoPlayer if it was built with one
if (exoPlayer.getTrackSelector() instanceof DefaultTrackSelector) {
trackSelector = (DefaultTrackSelector) exoPlayer.getTrackSelector();
}

exoPlayer.setMediaItem(mediaItem);
exoPlayer.prepare();
exoPlayer.addListener(createExoPlayerEventListener(exoPlayer, surfaceProducer));
Expand Down Expand Up @@ -122,6 +139,112 @@ public ExoPlayer getExoPlayer() {
return exoPlayer;
}

@UnstableApi
@Override
public @NonNull NativeAudioTrackData getAudioTracks() {
List<ExoPlayerAudioTrackData> audioTracks = new ArrayList<>();

// Get the current tracks from ExoPlayer
Tracks tracks = exoPlayer.getCurrentTracks();

// Iterate through all track groups
for (int groupIndex = 0; groupIndex < tracks.getGroups().size(); groupIndex++) {
Tracks.Group group = tracks.getGroups().get(groupIndex);

// Only process audio tracks
if (group.getType() == C.TRACK_TYPE_AUDIO) {
for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
Format format = group.getTrackFormat(trackIndex);
boolean isSelected = group.isTrackSelected(trackIndex);

// Create audio track data with metadata
ExoPlayerAudioTrackData audioTrack =
new ExoPlayerAudioTrackData(
(long) groupIndex,
(long) trackIndex,
format.label,
format.language,
isSelected,
format.bitrate != Format.NO_VALUE ? (long) format.bitrate : null,
format.sampleRate != Format.NO_VALUE ? (long) format.sampleRate : null,
format.channelCount != Format.NO_VALUE ? (long) format.channelCount : null,
format.codecs != null ? format.codecs : null);

audioTracks.add(audioTrack);
}
}
}
return new NativeAudioTrackData(audioTracks);
}

@UnstableApi
@Override
public void selectAudioTrack(long groupIndex, long trackIndex) {
if (trackSelector == null) {
Log.w("VideoPlayer", "Cannot select audio track: track selector is null");
return;
}

try {

// Get current tracks
Tracks tracks = exoPlayer.getCurrentTracks();

if (groupIndex >= tracks.getGroups().size()) {
Log.w(
"VideoPlayer",
"Cannot select audio track: groupIndex "
+ groupIndex
+ " is out of bounds (available groups: "
+ tracks.getGroups().size()
+ ")");
return;
}

Tracks.Group group = tracks.getGroups().get((int) groupIndex);

// Verify it's an audio track and the track index is valid
if (group.getType() != C.TRACK_TYPE_AUDIO || (int) trackIndex >= group.length) {
if (group.getType() != C.TRACK_TYPE_AUDIO) {
Log.w(
"VideoPlayer",
"Cannot select audio track: group at index "
+ groupIndex
+ " is not an audio track (type: "
+ group.getType()
+ ")");
} else {
Log.w(
"VideoPlayer",
"Cannot select audio track: trackIndex "
+ trackIndex
+ " is out of bounds (available tracks in group: "
+ group.length
+ ")");
}
return;
}

// Get the track group and create a selection override
TrackGroup trackGroup = group.getMediaTrackGroup();
TrackSelectionOverride override = new TrackSelectionOverride(trackGroup, (int) trackIndex);

// Apply the track selection override
trackSelector.setParameters(
trackSelector.buildUponParameters().setOverrideForType(override).build());

} catch (ArrayIndexOutOfBoundsException e) {
Log.w(
"VideoPlayer",
"Cannot select audio track: invalid indices (groupIndex: "
+ groupIndex
+ ", trackIndex: "
+ trackIndex
+ "). "
+ e.getMessage());
}
}

public void dispose() {
if (disposeHandler != null) {
disposeHandler.onDispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ public interface VideoPlayerCallbacks {
void onError(@NonNull String code, @Nullable String message, @Nullable Object details);

void onIsPlayingStateUpdate(boolean isPlaying);

void onAudioTrackChanged(@Nullable String selectedTrackId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,9 @@ public void onError(@NonNull String code, @Nullable String message, @Nullable Ob
public void onIsPlayingStateUpdate(boolean isPlaying) {
eventSink.success(new IsPlayingStateEvent(isPlaying));
}

@Override
public void onAudioTrackChanged(@Nullable String selectedTrackId) {
eventSink.success(new AudioTrackChangedEvent(selectedTrackId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.ExoPlayer;
import io.flutter.plugins.videoplayer.ExoPlayerEventListener;
import io.flutter.plugins.videoplayer.VideoAsset;
Expand All @@ -22,6 +23,7 @@
* displaying the video in the app.
*/
public class PlatformViewVideoPlayer extends VideoPlayer {
@UnstableApi
@VisibleForTesting
public PlatformViewVideoPlayer(
@NonNull VideoPlayerCallbacks events,
Expand All @@ -40,6 +42,7 @@ public PlatformViewVideoPlayer(
* @param options options for playback.
* @return a video player instance.
*/
@UnstableApi
@NonNull
public static PlatformViewVideoPlayer create(
@NonNull Context context,
Expand All @@ -51,8 +54,11 @@ public static PlatformViewVideoPlayer create(
asset.getMediaItem(),
options,
() -> {
androidx.media3.exoplayer.trackselection.DefaultTrackSelector trackSelector =
new androidx.media3.exoplayer.trackselection.DefaultTrackSelector(context);
ExoPlayer.Builder builder =
new ExoPlayer.Builder(context)
.setTrackSelector(trackSelector)
.setMediaSourceFactory(asset.getMediaSourceFactory(context));
return builder.build();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.ExoPlayer;
import io.flutter.plugins.videoplayer.ExoPlayerEventListener;
import io.flutter.plugins.videoplayer.VideoAsset;
Expand Down Expand Up @@ -39,6 +40,7 @@ public final class TextureVideoPlayer extends VideoPlayer implements SurfaceProd
* @param options options for playback.
* @return a video player instance.
*/
@UnstableApi
@NonNull
public static TextureVideoPlayer create(
@NonNull Context context,
Expand All @@ -52,13 +54,17 @@ public static TextureVideoPlayer create(
asset.getMediaItem(),
options,
() -> {
androidx.media3.exoplayer.trackselection.DefaultTrackSelector trackSelector =
new androidx.media3.exoplayer.trackselection.DefaultTrackSelector(context);
ExoPlayer.Builder builder =
new ExoPlayer.Builder(context)
.setTrackSelector(trackSelector)
.setMediaSourceFactory(asset.getMediaSourceFactory(context));
return builder.build();
});
}

@UnstableApi
@VisibleForTesting
public TextureVideoPlayer(
@NonNull VideoPlayerCallbacks events,
Expand Down
Loading