如何在 ExoPlayer 中跳过部分视频 android
How to skip few part of the video in ExoPlayer android
我想在我的视频播放器中实现跳过简介、跳过演职员表等功能。我正在使用 Exoplayer,假设我有一个长度为 00:15:21 (hh:mm:ss) 的视频,我知道视频的实际内容从 00:00:18 开始,内容结束于 [=20] =].我想像 Netflix 一样显示 "Skip intro" 和 "Next Episode" 按钮。我怎样才能做到这一点?
Exoplayer 页面上的问题Github:
https://github.com/google/ExoPlayer/issues/5515
从Developer Guide开始,您可以使用ClippingMediaSource
API来完成您的任务。
ClippingMediaSource can be used to clip a MediaSource so that only part of it is played
从 00:00:18 开始播放视频到结尾(跳过介绍)。
MediaSource videoSource =
new ExtractorMediaSource.Factory(...).createMediaSource(videoUri);
// Clip to start at from 00:00:18 to the end.
ClippingMediaSource clippingSource =
new ClippingMediaSource(
videoSource,
/* startPositionUs= */ 18_000_000,
/* endPositionUs= */ C.TIME_END_OF_SOURCE);
从 00:14:12 开始播放视频到结束(下一集)
MediaSource videoSource =
new ExtractorMediaSource.Factory(...).createMediaSource(videoUri);
// Clip to start at from 00:14:12 to the end.
ClippingMediaSource clippingSource =
new ClippingMediaSource(
videoSource,
/* startPositionUs= */ 852_000_000,
/* endPositionUs= */ C.TIME_END_OF_SOURCE);
或者播放从 00:00:18 到 00:14:12
的视频
MediaSource videoSource =
new ExtractorMediaSource.Factory(...).createMediaSource(videoUri);
// Clip to start at from 00:00:18 to 00:14:12.
ClippingMediaSource clippingSource =
new ClippingMediaSource(
videoSource,
/* startPositionUs= */ 18_000_000,
/* endPositionUs= */ 852_000_000);
您可以找到有关 API here 的更多信息。
好的,在 ExoPlayer 开发人员的帮助下,我找到了解决方案。详细信息在我在问题中添加的参考 link 中。这是摘要:
如果我们知道跳过的持续时间,我们可以创建消息并将其发送到 Exoplayer,在回调中,我们可以实现我们的业务逻辑,例如使按钮可见、按钮的 onClickListners 等。
private class PlayerEventListener extends Player.DefaultEventListener {
@Override
public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
prepareSkipToNextEpisode();
if(reason == DISCONTINUITY_REASON_PERIOD_TRANSITION){ //when a video naturally ends it course and starts playing next video.
prepareSkipIntro();
}
}
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
super.onTracksChanged(trackGroups, trackSelections);
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
String stateString;
switch (playbackState) {
case Player.STATE_IDLE: // The player does not have any media to play.
stateString = "Player.STATE_IDLE";
mProgressBar.setVisibility(View.VISIBLE);
playerView.setKeepScreenOn(false);
//mPlayerView.hideController();
mediaControlsLayout.setVisibility(View.GONE);
break;
case Player.STATE_BUFFERING: // The player needs to load media before playing.
stateString = "Player.STATE_BUFFERING";
mProgressBar.setVisibility(View.VISIBLE);
mediaControlsLayout.setVisibility(View.GONE);
playerView.setKeepScreenOn(true);
break;
case Player.STATE_READY: // The player is able to immediately play from its current position.
stateString = "Player.STATE_READY";
mProgressBar.setVisibility(View.GONE);
mediaControlsLayout.setVisibility(View.VISIBLE);
playerView.setKeepScreenOn(true);
prepareSkipToNextEpisode();
if((player.getContentPosition() < 5000)) {
prepareSkipIntro();
}
//prepareSkipToNextEpisode();
break;
case Player.STATE_ENDED: // The player has finished playing the media.
stateString = "Player.STATE_ENDED";
playerView.setKeepScreenOn(false);
break;
default:
stateString = "UNKNOWN_STATE";
break;
}
}
}
private void prepareSkipIntro(){
if(episodeStartTimes[player.getCurrentWindowIndex()] > 0) {
inttoBeginsHandler = new Handler();
long introDurationMs = 3 * 1000;
player.createMessage((messageType, payload) -> hideSkipIntro())
.setPosition(introDurationMs).setHandler(inttoBeginsHandler).send();
}
}
private void hideSkipIntro(){
if(player.getCurrentPosition()>2500) {
skipIntroBtn.setVisibility(View.VISIBLE);
skipIntroBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (player != null) {
player.seekTo(episodeStartTimes[player.getCurrentWindowIndex()]);
prepareSkipToNextEpisode();
skipIntroBtn.setVisibility(View.GONE);
}
}
});
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
skipIntroBtn.setVisibility(View.GONE);
}
}, 5000);
}
}
private void prepareSkipToNextEpisode(){
if(episodeEndTimes[player.getCurrentWindowIndex()] > 0) {
if (player.getCurrentPosition() < 1000) {
remainingTime = episodeDummyEndtimes[player.getCurrentWindowIndex()];
} else {
remainingTime = episodeDummyEndtimes[player.getCurrentWindowIndex()] - player.getCurrentPosition();
}
creditsBeginssHandler = null;
creditsBeginssHandler = new Handler();
player.createMessage((messageType, payload) -> endEpisode())
.setPosition(remainingTime).setHandler(creditsBeginssHandler).send();
}
}
private void endEpisode(){
if(player.getCurrentPosition()>= episodeDummyEndtimes[player.getCurrentWindowIndex()]) {
if (player.getCurrentWindowIndex() < (episodeEndTimes.length-1)) { // no need of skip credits for the final episode
skipCreditsBtn.setVisibility(View.VISIBLE);
skipCreditsBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
player.seekTo(player.getNextWindowIndex(), 0);
skipCreditsBtn.setVisibility(View.GONE);
}
});
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
skipCreditsBtn.setVisibility(View.GONE);
}
}, 5000);
}else{
skipCreditsBtn.setVisibility(View.GONE);
}
}
}
这里我一般把00:15:00作为视频内容的结尾,注意,如果用户使用seekbar手动移动超过该时长,则不会触发该消息。
这里是执行此操作的简短方法。
下面的代码在开始时剪辑视频最多 13 秒以跳过介绍。
视频源是 HlsMediaSource
但您可以对 VideoSource
.
进行类似的操作
DefaultHttpDataSourceFactory dataSourceFactory = getSettedHeadersDataFactory();
HlsMediaSource hlsMediaSource =
new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse(qualities.get(currentQuality).getQualityUrl()));
ClippingMediaSource clippingSource =
new ClippingMediaSource(
hlsMediaSource,
/* startPositionUs= */ 13_000_000,
/* endPositionUs= */ C.TIME_END_OF_SOURCE);
player.prepare(clippingSource);
我想在我的视频播放器中实现跳过简介、跳过演职员表等功能。我正在使用 Exoplayer,假设我有一个长度为 00:15:21 (hh:mm:ss) 的视频,我知道视频的实际内容从 00:00:18 开始,内容结束于 [=20] =].我想像 Netflix 一样显示 "Skip intro" 和 "Next Episode" 按钮。我怎样才能做到这一点?
Exoplayer 页面上的问题Github: https://github.com/google/ExoPlayer/issues/5515
从Developer Guide开始,您可以使用ClippingMediaSource
API来完成您的任务。
ClippingMediaSource can be used to clip a MediaSource so that only part of it is played
从 00:00:18 开始播放视频到结尾(跳过介绍)。
MediaSource videoSource =
new ExtractorMediaSource.Factory(...).createMediaSource(videoUri);
// Clip to start at from 00:00:18 to the end.
ClippingMediaSource clippingSource =
new ClippingMediaSource(
videoSource,
/* startPositionUs= */ 18_000_000,
/* endPositionUs= */ C.TIME_END_OF_SOURCE);
从 00:14:12 开始播放视频到结束(下一集)
MediaSource videoSource =
new ExtractorMediaSource.Factory(...).createMediaSource(videoUri);
// Clip to start at from 00:14:12 to the end.
ClippingMediaSource clippingSource =
new ClippingMediaSource(
videoSource,
/* startPositionUs= */ 852_000_000,
/* endPositionUs= */ C.TIME_END_OF_SOURCE);
或者播放从 00:00:18 到 00:14:12
的视频MediaSource videoSource =
new ExtractorMediaSource.Factory(...).createMediaSource(videoUri);
// Clip to start at from 00:00:18 to 00:14:12.
ClippingMediaSource clippingSource =
new ClippingMediaSource(
videoSource,
/* startPositionUs= */ 18_000_000,
/* endPositionUs= */ 852_000_000);
您可以找到有关 API here 的更多信息。
好的,在 ExoPlayer 开发人员的帮助下,我找到了解决方案。详细信息在我在问题中添加的参考 link 中。这是摘要:
如果我们知道跳过的持续时间,我们可以创建消息并将其发送到 Exoplayer,在回调中,我们可以实现我们的业务逻辑,例如使按钮可见、按钮的 onClickListners 等。
private class PlayerEventListener extends Player.DefaultEventListener {
@Override
public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
prepareSkipToNextEpisode();
if(reason == DISCONTINUITY_REASON_PERIOD_TRANSITION){ //when a video naturally ends it course and starts playing next video.
prepareSkipIntro();
}
}
}
@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
super.onTracksChanged(trackGroups, trackSelections);
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
String stateString;
switch (playbackState) {
case Player.STATE_IDLE: // The player does not have any media to play.
stateString = "Player.STATE_IDLE";
mProgressBar.setVisibility(View.VISIBLE);
playerView.setKeepScreenOn(false);
//mPlayerView.hideController();
mediaControlsLayout.setVisibility(View.GONE);
break;
case Player.STATE_BUFFERING: // The player needs to load media before playing.
stateString = "Player.STATE_BUFFERING";
mProgressBar.setVisibility(View.VISIBLE);
mediaControlsLayout.setVisibility(View.GONE);
playerView.setKeepScreenOn(true);
break;
case Player.STATE_READY: // The player is able to immediately play from its current position.
stateString = "Player.STATE_READY";
mProgressBar.setVisibility(View.GONE);
mediaControlsLayout.setVisibility(View.VISIBLE);
playerView.setKeepScreenOn(true);
prepareSkipToNextEpisode();
if((player.getContentPosition() < 5000)) {
prepareSkipIntro();
}
//prepareSkipToNextEpisode();
break;
case Player.STATE_ENDED: // The player has finished playing the media.
stateString = "Player.STATE_ENDED";
playerView.setKeepScreenOn(false);
break;
default:
stateString = "UNKNOWN_STATE";
break;
}
}
}
private void prepareSkipIntro(){
if(episodeStartTimes[player.getCurrentWindowIndex()] > 0) {
inttoBeginsHandler = new Handler();
long introDurationMs = 3 * 1000;
player.createMessage((messageType, payload) -> hideSkipIntro())
.setPosition(introDurationMs).setHandler(inttoBeginsHandler).send();
}
}
private void hideSkipIntro(){
if(player.getCurrentPosition()>2500) {
skipIntroBtn.setVisibility(View.VISIBLE);
skipIntroBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (player != null) {
player.seekTo(episodeStartTimes[player.getCurrentWindowIndex()]);
prepareSkipToNextEpisode();
skipIntroBtn.setVisibility(View.GONE);
}
}
});
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
skipIntroBtn.setVisibility(View.GONE);
}
}, 5000);
}
}
private void prepareSkipToNextEpisode(){
if(episodeEndTimes[player.getCurrentWindowIndex()] > 0) {
if (player.getCurrentPosition() < 1000) {
remainingTime = episodeDummyEndtimes[player.getCurrentWindowIndex()];
} else {
remainingTime = episodeDummyEndtimes[player.getCurrentWindowIndex()] - player.getCurrentPosition();
}
creditsBeginssHandler = null;
creditsBeginssHandler = new Handler();
player.createMessage((messageType, payload) -> endEpisode())
.setPosition(remainingTime).setHandler(creditsBeginssHandler).send();
}
}
private void endEpisode(){
if(player.getCurrentPosition()>= episodeDummyEndtimes[player.getCurrentWindowIndex()]) {
if (player.getCurrentWindowIndex() < (episodeEndTimes.length-1)) { // no need of skip credits for the final episode
skipCreditsBtn.setVisibility(View.VISIBLE);
skipCreditsBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
player.seekTo(player.getNextWindowIndex(), 0);
skipCreditsBtn.setVisibility(View.GONE);
}
});
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
skipCreditsBtn.setVisibility(View.GONE);
}
}, 5000);
}else{
skipCreditsBtn.setVisibility(View.GONE);
}
}
}
这里我一般把00:15:00作为视频内容的结尾,注意,如果用户使用seekbar手动移动超过该时长,则不会触发该消息。
这里是执行此操作的简短方法。
下面的代码在开始时剪辑视频最多 13 秒以跳过介绍。
视频源是 HlsMediaSource
但您可以对 VideoSource
.
DefaultHttpDataSourceFactory dataSourceFactory = getSettedHeadersDataFactory();
HlsMediaSource hlsMediaSource =
new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse(qualities.get(currentQuality).getQualityUrl()));
ClippingMediaSource clippingSource =
new ClippingMediaSource(
hlsMediaSource,
/* startPositionUs= */ 13_000_000,
/* endPositionUs= */ C.TIME_END_OF_SOURCE);
player.prepare(clippingSource);