如何在 Exoplayer 上显示 HLS 嵌入式字幕

How to show HLS embedded captions on Exoplayer

如何使用 Exoplayer、ExoMedia 或其他播放器启用以及 select 嵌入在 HLS 格式的 Vimeo 视频中的不同字幕? 在 iOS 中,同一视频已经提供了本地字幕选项,但在 Android 中,我找不到实现它的方法。

我在这里的回答看起来很像 ,所以您可能想先检查一下。

ExoPlayer 是您想要 Android 的库。让字幕显示是一项非常重要的任务,但是 demo app for that library has all the code you'll need to get them working on an HLS video. More specifically the PlayerActivity class。您可以在演示应用程序中转到 HLS -> "Apple 16x9 basic stream",该视频有大量字幕(又名 "text tracks")。

只是为了简化他们的代码,使其不依赖于助手(因此您可以看到它是如何工作的 只是 在隐藏式字幕上)我 written/documented 下面是他们的一些代码。

private static class ClosedCaptionManager {

    ClosedCaptionManager(MappingTrackSelector mappingTrackSelector, SimpleExoPlayer player) {
        this.player = player;
        this.trackSelector = mappingTrackSelector;
    }

    SimpleExoPlayer player;
    MappingTrackSelector trackSelector;

    // These two could be fields OR passed around
    int textTrackIndex;
    TrackGroupArray trackGroups;

    ArrayList<Pair<Integer, Integer>> pairTrackList = new ArrayList<>();

    private boolean checkAndSetClosedCaptions() {
        // This is the body of the logic  for see if there are even video tracks
        // It also does some field setting
        MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
        if (mappedTrackInfo == null) {
            return false;
        }
        for (int i = 0; i < mappedTrackInfo.length; i++) {
            trackGroups = mappedTrackInfo.getTrackGroups(i);
            if (trackGroups.length != 0) {
                switch (player.getRendererType(i)) {
                    case C.TRACK_TYPE_TEXT:
                        textTrackIndex = i;
                        return true;
                }
            }
        }
        return false;
    }

    private void buildTrackList() {
        // This next part is actually about getting the list.
        // Below you'd be building up items in a list. This just does
        // views directly, but you could just have a list of track names (with indexes)
        for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
            TrackGroup group = trackGroups.get(groupIndex);
            for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
                if (trackIndex == 0) {
                    // Beginning of a new set, the demo app adds a divider
                }
                //CheckedTextView trackView = ...; // The TextView to show in the list
                // The below points to a util which extracts the quality from the TrackGroup
                //trackView.setText(DemoUtil.buildTrackName(group.getFormat(trackIndex)));
                Log.e("Thing", DemoUtil.buildTrackName(group.getFormat(trackIndex)));
                pairTrackList.add(new Pair<>(groupIndex, trackIndex));
            }
        }
    }

    private void onTrackViewClick(Pair<Integer, Integer> trackPair) {
        // Assuming you tagged the view with the groupIndex and trackIndex, you
        // can build your override with that info.
        Pair<Integer, Integer> tag = trackPair;
        int groupIndex = tag.first;
        int trackIndex = tag.second;
        // This is the override you'd use for something that isn't adaptive.
        // `override = new SelectionOverride(FIXED_FACTORY, groupIndex, trackIndex);`
        // Otherwise they call their helper for adaptives (HLS/DASH), which roughly does:
        int[] tracks = getTracksAdding(new MappingTrackSelector.SelectionOverride(
                        new FixedTrackSelection.Factory(), groupIndex, trackIndex),
                trackIndex
        );
        TrackSelection.Factory factory = tracks.length == 1
                ? new FixedTrackSelection.Factory()
                : new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);

        MappingTrackSelector.SelectionOverride override =
                new MappingTrackSelector.SelectionOverride(factory, groupIndex, tracks);

        // Then we actually set our override on the selector to switch the text track
        trackSelector.setSelectionOverride(textTrackIndex, trackGroups, override);
    }

    private static int[] getTracksAdding(MappingTrackSelector.SelectionOverride override, int addedTrack) {
        int[] tracks = override.tracks;
        tracks = Arrays.copyOf(tracks, tracks.length + 1);
        tracks[tracks.length - 1] = addedTrack;
        return tracks;
    }
}

然后,如果您 post 将以下代码放入其 initializePlayer() 方法的末尾,您将了解这些部分是如何组合在一起的。

    final ClosedCaptionManager closedCaptionManager = new ClosedCaptionManager(trackSelector, player);

    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            boolean hasTracks = closedCaptionManager.checkAndSetClosedCaptions();
            if (hasTracks) {
                closedCaptionManager.buildTrackList();
                closedCaptionManager.onTrackViewClick(closedCaptionManager.pairTrackList.get(4));
            }
        }
    }, 2000);

上面的代码非常草率,但至少应该能让您朝着正确的方向开始。我不建议使用所写内容的任何部分——主要是为了了解不同部分如何组合在一起。他们在演示应用程序中所拥有的要好一些,因为他们的代码对于不同的曲目选择类型非常可重用(因为您可以拥有视频、音频和文本曲目)。

这很好用!

TrackGroupArray trackGroups = mappedTrackInfo.getTrackGroups(rendererIndex);   

TrackSelectionArray currentTrackGroups = player.getCurrentTrackSelections();
TrackSelection currentTrackSelection = currentTrackGroups.get(rendererIndex);


    for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {

        TrackGroup group = trackGroups.get(groupIndex);

        for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
            Format trackFormat = group.getFormat(trackIndex);


            if(currentTrackSelection!=null && currentTrackSelection.getSelectedFormat()==trackFormat){
                //THIS ONE IS SELECTED
            }




        }
    }

rendererIndex 是视频的 0,音频的 1 和 Subtitles/Text

2