基线备份:thirdparty 变体完成 + Android 4.2 兼容改造前

包含内容:
- thirdparty flavor 独立 applicationId + Intent 入口(ThirdPartyEntryActivity)
- 58 后端接口适配(单 /api 路径、code 0/200、camelCase 字段、UNBOUND 轮询)
- MAC 硬件锚定校验(防一凭证多设备共用)
- EPG mac 覆盖 SDK init(sMacOverride)+ ChannelManger 初始化对齐
- 提示文案优化(设备未注册/权益未绑定)
This commit is contained in:
zhaogd
2026-08-25 11:16:40 +08:00
commit d11464d466
1287 changed files with 154945 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
package com.pukka.mediaplayertv;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.migu.mediaplayertv.test", appContext.getPackageName());
}
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.pukka.mediaplayertv">
<!-- <application android:allowBackup="false" android:label="${app_name}"-->
<!-- android:supportsRtl="true">-->
<!-- </application>-->
</manifest>

View File

@@ -0,0 +1,96 @@
package com.pukka.videoplayertv;
/**
* Author: wxf
* Date: 2025/1/6 13:44
* Description:播放器的抽象类封装
*/
@SuppressWarnings("WeakerAccess")
public abstract class AbstractMediaPlayer implements IMediaPlayer {
private OnPreparedListener mOnPreparedListener;
private OnCompletionListener mOnCompletionListener;
private OnBufferingUpdateListener mOnBufferingUpdateListener;
private OnSeekCompleteListener mOnSeekCompleteListener;
private OnVideoSizeChangedListener mOnVideoSizeChangedListener;
private OnErrorListener mOnErrorListener;
private OnInfoListener mOnInfoListener;
public final void setOnPreparedListener(OnPreparedListener listener) {
mOnPreparedListener = listener;
}
public final void setOnCompletionListener(OnCompletionListener listener) {
mOnCompletionListener = listener;
}
public final void setOnBufferingUpdateListener(
OnBufferingUpdateListener listener) {
mOnBufferingUpdateListener = listener;
}
public final void setOnSeekCompleteListener(OnSeekCompleteListener listener) {
mOnSeekCompleteListener = listener;
}
public final void setOnVideoSizeChangedListener(
OnVideoSizeChangedListener listener) {
mOnVideoSizeChangedListener = listener;
}
public final void setOnErrorListener(OnErrorListener listener) {
mOnErrorListener = listener;
}
public final void setOnInfoListener(OnInfoListener listener) {
mOnInfoListener = listener;
}
public void resetListeners() {
mOnPreparedListener = null;
mOnBufferingUpdateListener = null;
mOnCompletionListener = null;
mOnSeekCompleteListener = null;
mOnVideoSizeChangedListener = null;
mOnErrorListener = null;
mOnInfoListener = null;
}
protected final void notifyOnPrepared() {
if (mOnPreparedListener != null)
mOnPreparedListener.onPrepared(this);
}
protected final void notifyOnCompletion() {
if (mOnCompletionListener != null)
mOnCompletionListener.onCompletion(this);
}
protected final void notifyOnBufferingUpdate(int percent) {
if (mOnBufferingUpdateListener != null)
mOnBufferingUpdateListener.onBufferingUpdate(this, percent);
}
protected final void notifyOnSeekComplete() {
if (mOnSeekCompleteListener != null)
mOnSeekCompleteListener.onSeekComplete(this);
}
protected final void notifyOnVideoSizeChanged(int width, int height,
int sarNum, int sarDen) {
if (mOnVideoSizeChangedListener != null)
mOnVideoSizeChangedListener.onVideoSizeChanged(this, width, height,
sarNum, sarDen);
}
protected final boolean notifyOnError(int what, int extra) {
return mOnErrorListener != null && mOnErrorListener.onError(this, what, extra);
}
protected final boolean notifyOnInfo(int what, int extra) {
return mOnInfoListener != null && mOnInfoListener.onInfo(this, what, extra);
}
public void setDataSource(IMediaDataSource mediaDataSource) {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,44 @@
package com.pukka.videoplayertv;
import android.media.MediaFormat;
public class AndroidMediaFormat implements IMediaFormat {
private final MediaFormat mMediaFormat;
public AndroidMediaFormat(MediaFormat mediaFormat) {
mMediaFormat = mediaFormat;
}
@Override
public int getInteger(String name) {
if (mMediaFormat == null) {
return 0;
}
return mMediaFormat.getInteger(name);
}
@Override
public String getString(String name) {
if (mMediaFormat == null) {
return null;
}
return mMediaFormat.getString(name);
}
@Override
public String toString() {
StringBuilder out = new StringBuilder(128);
out.append(getClass().getName());
out.append('{');
if (mMediaFormat != null) {
out.append(mMediaFormat.toString());
} else {
out.append("null");
}
out.append('}');
return out.toString();
}
}

View File

@@ -0,0 +1,521 @@
/*
* Copyright (C) 2006 The Android Open Source Project
* Copyright (C) 2013 Zhang Rui <bbcallen@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pukka.videoplayertv;
import android.annotation.TargetApi;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaDataSource;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import com.pukka.libcore.threading.DispatchQueue;
import com.pukka.libcore.utils.LogUtils;
import java.io.FileDescriptor;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Map;
/**
* Author: wxf
* Date: 2025/1/6 13:44
* Description:原生播放器的封装
*/
public class AndroidMediaPlayer extends AbstractMediaPlayer {
private static final String TAG = "VideoViewTv";
private final MediaPlayer mInternalMediaPlayer;
private final AndroidMediaPlayerListenerHolder mInternalListenerAdapter;
private String mDataSource;
private MediaDataSource mMediaDataSource;
private final Object mInitLock = new Object();
private boolean mIsReleased;
private static MediaInfo sMediaInfo;
public AndroidMediaPlayer() {
synchronized (mInitLock) {
mInternalMediaPlayer = new MediaPlayer();
}
mInternalMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mInternalListenerAdapter = new AndroidMediaPlayerListenerHolder(this);
attachInternalListeners();
}
public MediaPlayer getInternalMediaPlayer() {
return mInternalMediaPlayer;
}
@Override
public void setDisplay(SurfaceHolder sh) {
synchronized (mInitLock) {
if (!mIsReleased) {
LogUtils.d("setDisplay ---xx : " + sh);
try {
mInternalMediaPlayer.setDisplay(sh);
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
}
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void setSurface(Surface surface) {
LogUtils.d("setSurface ---xx : " + surface);
mInternalMediaPlayer.setSurface(surface);
}
@Override
public void setDataSource(Context context, Uri uri)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
try {
LogUtils.d("setDataSource ---xx : ");
mInternalMediaPlayer.setDataSource(context, uri);
} catch (IOException | IllegalArgumentException | SecurityException |
IllegalStateException e) {
e.printStackTrace();
}
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void setDataSource(Context context, Uri uri, Map<String, String> headers)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
try {
LogUtils.d("setDataSource ---yy : ");
mInternalMediaPlayer.setDataSource(context, uri, headers);
} catch (IOException | IllegalArgumentException | SecurityException |
IllegalStateException e) {
e.printStackTrace();
}
}
@Override
public void setDataSource(FileDescriptor fd)
throws IOException, IllegalArgumentException, IllegalStateException {
try {
mInternalMediaPlayer.setDataSource(fd);
} catch (IOException | IllegalArgumentException | SecurityException |
IllegalStateException e) {
e.printStackTrace();
}
}
@Override
public void setDataSource(String path) throws IOException,
IllegalArgumentException, SecurityException, IllegalStateException {
mDataSource = path;
Uri uri = Uri.parse(path);
String scheme = uri.getScheme();
if (!TextUtils.isEmpty(scheme) && scheme.equalsIgnoreCase("file")) {
mInternalMediaPlayer.setDataSource(uri.getPath());
} else {
mInternalMediaPlayer.setDataSource(path);
}
}
@TargetApi(Build.VERSION_CODES.M)
@Override
public void setDataSource(IMediaDataSource mediaDataSource) {
LogUtils.d("setDataSource ---zz : ");
releaseMediaDataSource();
mMediaDataSource = new MediaDataSourceProxy(mediaDataSource);
mInternalMediaPlayer.setDataSource(mMediaDataSource);
LogUtils.d("setDataSource ---end : ");
}
@Override
public int getBufferPercentage() {
return 0;
}
@TargetApi(Build.VERSION_CODES.M)
private static class MediaDataSourceProxy extends MediaDataSource {
private final IMediaDataSource mMediaDataSource;
public MediaDataSourceProxy(IMediaDataSource mediaDataSource) {
LogUtils.d("mMediaDataSource ---MediaDataSourceProxy : ");
mMediaDataSource = mediaDataSource;
}
@Override
public int readAt(long position, byte[] buffer, int offset, int size) throws IOException {
LogUtils.d("mMediaDataSource ---readAt : ");
return mMediaDataSource.readAt(position, buffer, offset, size);
}
@Override
public long getSize() throws IOException {
LogUtils.d("mMediaDataSource ---getSize : ");
return mMediaDataSource.getSize();
}
@Override
public void close() throws IOException {
LogUtils.d("mMediaDataSource ---close : ");
mMediaDataSource.close();
}
}
@Override
public String getDataSource() {
return mDataSource;
}
private void releaseMediaDataSource() {
if (mMediaDataSource != null) {
LogUtils.d("releaseMediaDataSource --- : ");
try {
mMediaDataSource.close();
} catch (IOException e) {
e.printStackTrace();
}
mMediaDataSource = null;
}
}
@Override
public void prepareAsync() throws IllegalStateException {
LogUtils.d("prepareAsync ---xx : ");
try {
mInternalMediaPlayer.prepareAsync();
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
@Override
public void start() throws IllegalStateException {
LogUtils.d("start ---xx : ");
try {
mInternalMediaPlayer.start();
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
@Override
public void stop() throws IllegalStateException {
LogUtils.d("stop ---xx : ");
try {
mInternalMediaPlayer.stop();
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
@Override
public void pause() throws IllegalStateException {
LogUtils.d("pause ---xx : ");
try {
mInternalMediaPlayer.pause();
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
@Override
public void setScreenOnWhilePlaying(boolean screenOn) {
mInternalMediaPlayer.setScreenOnWhilePlaying(screenOn);
}
@Override
public ITrackInfo[] getTrackInfo() {
return AndroidTrackInfo.fromMediaPlayer(mInternalMediaPlayer);
}
@Override
public int getVideoWidth() {
return mInternalMediaPlayer.getVideoWidth();
}
@Override
public int getVideoHeight() {
return mInternalMediaPlayer.getVideoHeight();
}
@Override
public int getVideoSarNum() {
return 1;
}
@Override
public int getVideoSarDen() {
return 1;
}
@Override
public boolean isPlaying() {
try {
return mInternalMediaPlayer.isPlaying();
} catch (IllegalStateException e) {
e.printStackTrace();
return false;
}
}
@Override
public void seekTo(long msec) throws IllegalStateException {
try {
mInternalMediaPlayer.seekTo((int) msec);
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
@Override
public long getCurrentPosition() {
try {
return mInternalMediaPlayer.getCurrentPosition();
} catch (IllegalStateException e) {
e.printStackTrace();
return 0;
}
}
@Override
public long getDuration() {
try {
return mInternalMediaPlayer.getDuration();
} catch (IllegalStateException e) {
e.printStackTrace();
return 0;
}
}
@Override
public void release() {
Log.d(TAG, "Android MediaPlayer -- 进入真正release播放器");
try {
mIsReleased = true;
mInternalMediaPlayer.release();
releaseMediaDataSource();
resetListeners();
attachInternalListeners();
} catch (IllegalStateException e) {
e.printStackTrace();
}
}
@Override
public void reset() {
try {
mInternalMediaPlayer.reset();
} catch (IllegalStateException e) {
e.printStackTrace();
}
releaseMediaDataSource();
resetListeners();
attachInternalListeners();
}
@Override
public float getSpeed() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
float currentSpeed = mInternalMediaPlayer.getPlaybackParams().getSpeed();
if(currentSpeed==1.0){
currentSpeed =1;
}
Log.d("PlaybackSpeed", "当前播放速度: " + currentSpeed + "x");
return currentSpeed;
} else {
Log.d("PlaybackSpeed", "API 23 以下不支持获取播放速度");
}
return 1;
}
@Override
public void setLooping(boolean looping) {
mInternalMediaPlayer.setLooping(looping);
}
@Override
public boolean isLooping() {
return mInternalMediaPlayer.isLooping();
}
@Override
public void setVolume(float leftVolume, float rightVolume) {
mInternalMediaPlayer.setVolume(leftVolume, rightVolume);
}
@Override
public int getAudioSessionId() {
return mInternalMediaPlayer.getAudioSessionId();
}
@Override
public MediaInfo getMediaInfo() {
if (sMediaInfo == null) {
MediaInfo module = new MediaInfo();
module.mVideoDecoder = "android";
module.mVideoDecoderImpl = "HW";
module.mAudioDecoder = "android";
module.mAudioDecoderImpl = "HW";
sMediaInfo = module;
}
return sMediaInfo;
}
@Override
public boolean isPlayable() {
return true;
}
/*--------------------
* misc
*/
@Override
public void setWakeMode(Context context, int mode) {
mInternalMediaPlayer.setWakeMode(context, mode);
}
@Override
public void setAudioStreamType(int streamtype) {
mInternalMediaPlayer.setAudioStreamType(streamtype);
}
@Override
public void setKeepInBackground(boolean keepInBackground) {
}
/**
* 倍速播放
*
* @param speed 倍速 2f
*/
@Override
public void setSpeed(float speed) {
LogUtils.d(TAG, "setSpeed: " + speed);
// 设置播放速度 (0.5x-2.0x 范围,具体取决于设备支持) Android 6.0 (API 23)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mInternalMediaPlayer.setPlaybackParams(mInternalMediaPlayer.getPlaybackParams()
.setSpeed(1.5f)); // 1.5倍速
}
}
/*--------------------
* Listeners adapter
*/
private void attachInternalListeners() {
mInternalMediaPlayer.setOnPreparedListener(mInternalListenerAdapter);
mInternalMediaPlayer
.setOnBufferingUpdateListener(mInternalListenerAdapter);
mInternalMediaPlayer.setOnCompletionListener(mInternalListenerAdapter);
mInternalMediaPlayer
.setOnSeekCompleteListener(mInternalListenerAdapter);
mInternalMediaPlayer
.setOnVideoSizeChangedListener(mInternalListenerAdapter);
mInternalMediaPlayer.setOnErrorListener(mInternalListenerAdapter);
mInternalMediaPlayer.setOnInfoListener(mInternalListenerAdapter);
}
private class AndroidMediaPlayerListenerHolder implements
MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener,
MediaPlayer.OnBufferingUpdateListener,
MediaPlayer.OnSeekCompleteListener,
MediaPlayer.OnVideoSizeChangedListener,
MediaPlayer.OnErrorListener, MediaPlayer.OnInfoListener {
public final WeakReference<AndroidMediaPlayer> mWeakMediaPlayer;
public AndroidMediaPlayerListenerHolder(AndroidMediaPlayer mp) {
mWeakMediaPlayer = new WeakReference<AndroidMediaPlayer>(mp);
}
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
AndroidMediaPlayer self = mWeakMediaPlayer.get();
return self != null && notifyOnInfo(what, extra);
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
AndroidMediaPlayer self = mWeakMediaPlayer.get();
LogUtils.d("onError ---xx --what : " + what + "--extra -- : " + extra);
return self != null && notifyOnError(what, extra);
}
@Override
public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
AndroidMediaPlayer self = mWeakMediaPlayer.get();
if (self == null) {
return;
}
notifyOnVideoSizeChanged(width, height, 1, 1);
}
@Override
public void onSeekComplete(MediaPlayer mp) {
AndroidMediaPlayer self = mWeakMediaPlayer.get();
if (self == null) {
return;
}
notifyOnSeekComplete();
}
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
AndroidMediaPlayer self = mWeakMediaPlayer.get();
if (self == null) {
return;
}
notifyOnBufferingUpdate(percent);
}
@Override
public void onCompletion(MediaPlayer mp) {
AndroidMediaPlayer self = mWeakMediaPlayer.get();
if (self == null) {
return;
}
notifyOnCompletion();
}
@Override
public void onPrepared(MediaPlayer mp) {
AndroidMediaPlayer self = mWeakMediaPlayer.get();
if (self == null) {
return;
}
notifyOnPrepared();
}
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pukka.videoplayertv;
import android.annotation.TargetApi;
import android.media.MediaFormat;
import android.media.MediaPlayer;
import android.os.Build;
public class AndroidTrackInfo implements ITrackInfo {
private final MediaPlayer.TrackInfo mTrackInfo;
public static AndroidTrackInfo[] fromMediaPlayer(MediaPlayer mp) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
return fromTrackInfo(mp.getTrackInfo());
}
return null;
}
private static AndroidTrackInfo[] fromTrackInfo(MediaPlayer.TrackInfo[] trackInfos) {
if (trackInfos == null) {
return null;
}
AndroidTrackInfo[] androidTrackInfo = new AndroidTrackInfo[trackInfos.length];
for (int i = 0; i < trackInfos.length; ++i) {
androidTrackInfo[i] = new AndroidTrackInfo(trackInfos[i]);
}
return androidTrackInfo;
}
private AndroidTrackInfo(MediaPlayer.TrackInfo trackInfo) {
mTrackInfo = trackInfo;
}
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public IMediaFormat getFormat() {
if (mTrackInfo == null) {
return null;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return null;
}
MediaFormat mediaFormat = mTrackInfo.getFormat();
if (mediaFormat == null) {
return null;
}
return new AndroidMediaFormat(mediaFormat);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public String getLanguage() {
if (mTrackInfo == null) {
return "und";
}
return mTrackInfo.getLanguage();
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public int getTrackType() {
if (mTrackInfo == null) {
return MEDIA_TRACK_TYPE_UNKNOWN;
}
return mTrackInfo.getTrackType();
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public String toString() {
StringBuilder out = new StringBuilder(128);
out.append(getClass().getSimpleName());
out.append('{');
if (mTrackInfo != null) {
out.append(mTrackInfo.toString());
} else {
out.append("null");
}
out.append('}');
return out.toString();
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public String getInfoInline() {
if (mTrackInfo != null) {
return mTrackInfo.toString();
} else {
return "null";
}
}
}

View File

@@ -0,0 +1,66 @@
package com.pukka.videoplayertv;
import com.pukka.libcore.boxShared.BoxSharedParamsUtils;
import com.pukka.libcore.utils.LogUtils;
import com.pukka.libcore.utils.StringUtils;
/**
* Author: wxf
* Date: 2023/8/11 13:44
* Description:盒子播放器没有第一帧回调(info=3)的兼容处理
*/
public class ConfigPlayer {
private static ConfigPlayer configPlayer;
public static ConfigPlayer getInstance() {
if (configPlayer == null) {
synchronized (ConfigPlayer.class) {
if (configPlayer == null) {
configPlayer = new ConfigPlayer();
}
}
}
return configPlayer;
}
//适配的渠道名单 : 青海
public String[] channelIds = {"400500000010315","400500000010321","400500000010319" };
//适配的盒子型号
public String[] models = {"b860av2.1" };
//当前渠道号
//private String channelId = BoxSharedParameters.getInstance().getChannelId();
private String channelId;
//当前的盒子型号
private String model = BoxSharedParamsUtils.getInstance().getModel();
/**
* 判断是否兼容盒子未返回第一帧(info=3)
* @return true 兼容;false 不兼容
**/
public boolean isNeedStartFirstFrame() {
boolean isNeed = false;
if (!StringUtils.isEmpty(channelId) && !StringUtils.isEmpty(model)) {
boolean isHaveChannel = false;
for (int i=0;i<channelIds.length;i++){
if(channelIds[i].equals(channelId)){
isHaveChannel = true;
}
}
boolean isHaveModel = false;
for (int j=0;j<models.length;j++){
if(model.toLowerCase().contains(models[j])){
isHaveModel = true;
}
}
if (isHaveChannel && isHaveModel){
isNeed = true;
}
}
LogUtils.d("channelId:"+channelId+" model:"+model);
LogUtils.d("isNeed:"+isNeed);
return isNeed;
}
}

View File

@@ -0,0 +1,49 @@
package com.pukka.videoplayertv;
import android.view.ViewGroup;
/**
* 播控相关接口
* */
public interface IMediaController {
/**
* 隐藏播控
* */
void hide();
/**
* 播控是否显示
* */
boolean isShowing();
/**
* 播控绑定到VideoViewTv控件上
* */
void setAnchorView(ViewGroup view);
/**
* 设置播控状态
* */
void setEnabled(boolean enabled);
/**
* 控制设置到VideoView对象上,VideoViewTv实现了MediaPlayerControl接口
* */
void setMediaPlayer(MediaPlayerControl player);
/**
* 在屏幕上显示这个控制器。它将在闲置’超时 (timeout)’毫秒到达后自动消失。
* */
void show(int timeout);
/**
* 在屏幕上显示这个控制器。它将在3秒以后自动消失。
*/
void show();
/**
* @param isffwd true 快进 false 快退
* 快进
* */
void ffwd(boolean isffwd);
/**
* 快进和快退的定位播放
* */
void seekToTime();
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pukka.videoplayertv;
import java.io.IOException;
@SuppressWarnings("RedundantThrows")
public interface IMediaDataSource {
int readAt(long position, byte[] buffer, int offset, int size) throws IOException;
long getSize() throws IOException;
void close() throws IOException;
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pukka.videoplayertv;
public interface IMediaFormat {
// Common keys
String KEY_MIME = "mime";
// Video Keys
String KEY_WIDTH = "width";
String KEY_HEIGHT = "height";
String getString(String name);
int getInteger(String name);
}

View File

@@ -0,0 +1,229 @@
package com.pukka.videoplayertv;
import android.annotation.TargetApi;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.view.Surface;
import android.view.SurfaceHolder;
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.Map;
/**
* 播放器相关接口定义和播放器相关常量
* */
public interface IMediaPlayer {
int MEDIA_INFO_UNKNOWN = 1;
int MEDIA_INFO_STARTED_AS_NEXT = 2;
/**第一帧 3*/
int MEDIA_INFO_VIDEO_RENDERING_START = 3;
int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700;
/**开始缓冲 701*/
int MEDIA_INFO_BUFFERING_START = 701;
/**结束缓冲 702*/
int MEDIA_INFO_BUFFERING_END = 702;
int MEDIA_INFO_NETWORK_BANDWIDTH = 703;
int MEDIA_INFO_BAD_INTERLEAVING = 800;
int MEDIA_INFO_NOT_SEEKABLE = 801;
int MEDIA_INFO_METADATA_UPDATE = 802;
int MEDIA_INFO_TIMED_TEXT_ERROR = 900;
int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901;
int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902;
int MEDIA_INFO_VIDEO_ROTATION_CHANGED = 10001;
int MEDIA_INFO_AUDIO_RENDERING_START = 10002;
int MEDIA_ERROR_UNKNOWN = 1;
int MEDIA_ERROR_SERVER_DIED = 100;
int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200;
/**IO异常 -1004*/
int MEDIA_ERROR_IO = -1004;
int MEDIA_ERROR_MALFORMED = -1007;
/**IO异常 -1010*/
int MEDIA_ERROR_UNSUPPORTED = -1010;
/**超时异常 -110*/
int MEDIA_ERROR_TIMED_OUT = -110;
void setDisplay(SurfaceHolder sh);
void setDataSource(Context context, Uri uri)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
void setDataSource(Context context, Uri uri, Map<String, String> headers)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
void setDataSource(FileDescriptor fd)
throws IOException, IllegalArgumentException, IllegalStateException;
/**
* 设置媒体资源
* */
void setDataSource(String path)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
/**
* 获取媒体资源
* */
String getDataSource();
/**
* 异步准备媒体资源
* */
void prepareAsync() throws IllegalStateException;
/**
* 开始播放
* */
void start() throws IllegalStateException;
/**
* 停止
* */
void stop() throws IllegalStateException;
/**
* 暂停
* */
void pause() throws IllegalStateException;
void setScreenOnWhilePlaying(boolean screenOn);
int getVideoWidth();
int getVideoHeight();
/**
* 是否在播放
* */
boolean isPlaying();
/**
* 定位播放
* */
void seekTo(long msec) throws IllegalStateException;
/**
* 获取当前播放时长
* */
long getCurrentPosition();
/**
* 获取播放总时长
* */
long getDuration();
/**
* 销毁播放器
* */
void release();
/**
* 复位播放器
* */
void reset();
/**
* 倍数播
* */
void setSpeed(float speed);
float getSpeed();
void setVolume(float leftVolume, float rightVolume);
int getAudioSessionId();
MediaInfo getMediaInfo();
@Deprecated
boolean isPlayable();
/**
* 播放准备就绪回调
* */
void setOnPreparedListener(OnPreparedListener listener);
/**
* 播放结束回调
* */
void setOnCompletionListener(OnCompletionListener listener);
/**
* 播放缓冲进度回调
* */
void setOnBufferingUpdateListener(
OnBufferingUpdateListener listener);
/**
* 定位播放结束回调
* */
void setOnSeekCompleteListener(
OnSeekCompleteListener listener);
void setOnVideoSizeChangedListener(
OnVideoSizeChangedListener listener);
/**
* 播放错误回调
* */
void setOnErrorListener(OnErrorListener listener);
/**
* 播放消息回调
* */
void setOnInfoListener(OnInfoListener listener);
/*--------------------
* Listeners
*/
interface OnPreparedListener {
void onPrepared(IMediaPlayer mp);
}
interface OnCompletionListener {
void onCompletion(IMediaPlayer mp);
}
interface OnBufferingUpdateListener {
void onBufferingUpdate(IMediaPlayer mp, int percent);
}
interface OnSeekCompleteListener {
void onSeekComplete(IMediaPlayer mp);
}
interface OnVideoSizeChangedListener {
void onVideoSizeChanged(IMediaPlayer mp, int width, int height,
int sar_num, int sar_den);
}
interface OnErrorListener {
boolean onError(IMediaPlayer mp, int what, int extra);
}
interface OnInfoListener {
boolean onInfo(IMediaPlayer mp, int what, int extra);
}
/*--------------------
* Optional
*/
void setAudioStreamType(int streamtype);
@Deprecated
void setKeepInBackground(boolean keepInBackground);
int getVideoSarNum();
int getVideoSarDen();
@Deprecated
void setWakeMode(Context context, int mode);
void setLooping(boolean looping);
boolean isLooping();
/*--------------------
* AndroidMediaPlayer: JELLY_BEAN
*/
ITrackInfo[] getTrackInfo();
/*--------------------
* AndroidMediaPlayer: ICE_CREAM_SANDWICH:
*/
void setSurface(Surface surface);
/*--------------------
* AndroidMediaPlayer: M:
*/
void setDataSource(IMediaDataSource mediaDataSource);
/**
* exoPlayer getBufferPercentage
* */
int getBufferPercentage();
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pukka.videoplayertv;
import android.graphics.SurfaceTexture;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* 画布相关接口
* */
public interface IRenderView {
int AR_ASPECT_FIT_PARENT = 0; // without clip 没有剪切 0视频分辨率小于盒子分辨率会自动拉伸铺满全屏 对于竖屏视频 只高度铺满 宽度不拉伸(一边铺满)
int AR_ASPECT_FILL_PARENT = 1; // may clip 剪切 强制全屏 所有类型的视频均会拉伸铺满
int AR_ASPECT_WRAP_CONTENT = 2; //原画幅,对于视频分辨率小于盒子分辨率的 会只展示中间一块内容 四周黑窗
int AR_MATCH_PARENT = 3;//铺满,
int AR_16_9_FIT_PARENT = 4;//拉伸。
int AR_4_3_FIT_PARENT = 5;//按比例缩放
/**
* 获得view
* */
View getView();
boolean shouldWaitForResize();
void setVideoSize(int videoWidth, int videoHeight);
void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen);
void setVideoRotation(int degree);
void setAspectRatio(int aspectRatio);
//添加回调
void addRenderCallback(@NonNull IRenderCallback callback);
//取消回调
void removeRenderCallback(@NonNull IRenderCallback callback);
interface ISurfaceHolder {
//绑定到视频播放器
void bindToMediaPlayer(IMediaPlayer mp);
@NonNull
IRenderView getRenderView();
@Nullable
SurfaceHolder getSurfaceHolder();
@Nullable
Surface openSurface();
@Nullable
SurfaceTexture getSurfaceTexture();
}
interface IRenderCallback {
/**
* @param holder
* @param width could be 0
* @param height could be 0
*/
void onSurfaceCreated(@NonNull ISurfaceHolder holder, int width, int height);
/**
* @param holder
* @param format could be 0
* @param width
* @param height
*/
void onSurfaceChanged(@NonNull ISurfaceHolder holder, int format, int width, int height);
void onSurfaceDestroyed(@NonNull ISurfaceHolder holder);
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pukka.videoplayertv;
import android.graphics.SurfaceTexture;
public interface ISurfaceTextureHolder {
void setSurfaceTexture(SurfaceTexture surfaceTexture);
SurfaceTexture getSurfaceTexture();
void setSurfaceTextureHost(ISurfaceTextureHost surfaceTextureHost);
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pukka.videoplayertv;
import android.graphics.SurfaceTexture;
public interface ISurfaceTextureHost {
void releaseSurfaceTexture(SurfaceTexture surfaceTexture);
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pukka.videoplayertv;
public interface ITrackInfo {
int MEDIA_TRACK_TYPE_AUDIO = 2;
int MEDIA_TRACK_TYPE_METADATA = 5;
int MEDIA_TRACK_TYPE_SUBTITLE = 4;
int MEDIA_TRACK_TYPE_TIMEDTEXT = 3;
int MEDIA_TRACK_TYPE_UNKNOWN = 0;
int MEDIA_TRACK_TYPE_VIDEO = 1;
IMediaFormat getFormat();
String getLanguage();
int getTrackType();
String getInfoInline();
}

View File

@@ -0,0 +1,235 @@
package com.pukka.videoplayertv;
import android.util.Log;
import android.view.View;
import com.pukka.libcore.utils.LogUtils;
import java.lang.ref.WeakReference;
/***
* 绘图测量帮助类
*/
public final class MeasureHelper {
private static final String TAG = "VideoViewTv";
private WeakReference<View> mWeakView;
//视频宽
private int mVideoWidth;
//视频高
private int mVideoHeight;
private int mVideoSarNum;
private int mVideoSarDen;
//视频旋转度
private int mVideoRotationDegree;
//测量宽度
private int mMeasuredWidth;
//测量高度
private int mMeasuredHeight;
//视频显示方式
private int mCurrentAspectRatio = IRenderView.AR_16_9_FIT_PARENT;
public MeasureHelper(View view) {
mWeakView = new WeakReference<View>(view);
}
public View getView() {
if (mWeakView == null) {
return null;
}
return mWeakView.get();
}
/**
* 设置视频的现实大小
*/
public void setVideoSize(int videoWidth, int videoHeight) {
mVideoWidth = videoWidth;
mVideoHeight = videoHeight;
}
/**
* 设置纵横比
*/
public void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen) {
mVideoSarNum = videoSarNum;
mVideoSarDen = videoSarDen;
}
/**
* 设置旋转度
*/
public void setVideoRotation(int videoRotationDegree) {
mVideoRotationDegree = videoRotationDegree;
}
/**
* Must be called by View.onMeasure(int, int)
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
public void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Log.i("wxf", "onMeasure(" + widthMeasureSpec + ", "
// + heightMeasureSpec + ")");
LogUtils.d("doMeasure --start-- - width : " + widthMeasureSpec + " videoHeight: " + heightMeasureSpec);
//切屏
if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) {
int tempSpec = widthMeasureSpec;
widthMeasureSpec = heightMeasureSpec;
heightMeasureSpec = tempSpec;
}
int width = View.getDefaultSize(mVideoWidth, widthMeasureSpec);
int height = View.getDefaultSize(mVideoHeight, heightMeasureSpec);
if (mCurrentAspectRatio == IRenderView.AR_MATCH_PARENT) {
width = widthMeasureSpec;
height = heightMeasureSpec;
} else if (mVideoWidth > 0 && mVideoHeight > 0) {
int widthSpecMode = View.MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = View.MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = View.MeasureSpec.getSize(heightMeasureSpec);
//MeasureSpec.AT_MOST (至多模式)
if (widthSpecMode == View.MeasureSpec.AT_MOST && heightSpecMode == View.MeasureSpec.AT_MOST) {
float specAspectRatio = (float) widthSpecSize / (float) heightSpecSize;
float displayAspectRatio;
Log.i("wxf", "onMeasure() mCurrentAspectRatio" + mCurrentAspectRatio);
switch (mCurrentAspectRatio) {
case IRenderView.AR_16_9_FIT_PARENT:
displayAspectRatio = 16.0f / 9.0f;
if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) {
displayAspectRatio = 1.0f / displayAspectRatio;
}
break;
case IRenderView.AR_4_3_FIT_PARENT:
displayAspectRatio = 4.0f / 3.0f;
if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) {
displayAspectRatio = 1.0f / displayAspectRatio;
}
break;
case IRenderView.AR_ASPECT_FIT_PARENT:
case IRenderView.AR_ASPECT_FILL_PARENT:
case IRenderView.AR_ASPECT_WRAP_CONTENT:
default:
displayAspectRatio = (float) mVideoWidth / (float) mVideoHeight;
if (mVideoSarNum > 0 && mVideoSarDen > 0) {
displayAspectRatio = displayAspectRatio * mVideoSarNum / mVideoSarDen;
}
break;
}
boolean shouldBeWider = displayAspectRatio > specAspectRatio;
LogUtils.d("是否固定宽度 shouldBeWider: " + shouldBeWider + "实际视频宽高比 displayAspectRatio: " + displayAspectRatio+" specAspectRatio:"+specAspectRatio);
switch (mCurrentAspectRatio) {
case IRenderView.AR_ASPECT_FIT_PARENT:
case IRenderView.AR_16_9_FIT_PARENT:
case IRenderView.AR_4_3_FIT_PARENT:
if (shouldBeWider) {
// too wide, fix width 太宽,固定宽度
LogUtils.d("固定宽度为屏幕宽度,高度保存比例缩放:"+mCurrentAspectRatio);
width = widthSpecSize;
height = (int) (width / displayAspectRatio);
} else {
// too high, fix height
LogUtils.d("固定高度为屏幕高度,宽度保存比例缩放:"+mCurrentAspectRatio);
height = heightSpecSize;
width = (int) (height * displayAspectRatio);
}
break;
case IRenderView.AR_ASPECT_FILL_PARENT:
if (shouldBeWider) {
// not high enough, fix height
LogUtils.d("AR_ASPECT_FILL_PARENT 固定高度,宽度保存比例缩放");
height = heightSpecSize;
width = (int) (height * displayAspectRatio);
} else {
// not wide enough, fix width
LogUtils.d("AR_ASPECT_FILL_PARENT 固定宽度,高度保存比例缩放");
width = widthSpecSize;
height = (int) (width / displayAspectRatio);
}
break;
case IRenderView.AR_ASPECT_WRAP_CONTENT:
default:
if (shouldBeWider) {
// too wide, fix width
width = Math.min(mVideoWidth, widthSpecSize);
height = (int) (width / displayAspectRatio);
} else {
// too high, fix height
height = Math.min(mVideoHeight, heightSpecSize);
width = (int) (height * displayAspectRatio);
}
break;
}
} else if (widthSpecMode == View.MeasureSpec.EXACTLY && heightSpecMode == View.MeasureSpec.EXACTLY) {
// the size is fixed
width = widthSpecSize;
height = heightSpecSize;
// for compatibility, we adjust size based on aspect ratio
if (mVideoWidth * height < width * mVideoHeight) {
//Log.i("@@@", "image too wide, correcting");
width = height * mVideoWidth / mVideoHeight;
} else if (mVideoWidth * height > width * mVideoHeight) {
//Log.i("@@@", "image too tall, correcting");
height = width * mVideoHeight / mVideoWidth;
}
} else if (widthSpecMode == View.MeasureSpec.EXACTLY) {
// only the width is fixed, adjust the height to match aspect ratio if possible
width = widthSpecSize;
height = width * mVideoHeight / mVideoWidth;
if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) {
// couldn't match aspect ratio within the constraints
height = heightSpecSize;
}
} else if (heightSpecMode == View.MeasureSpec.EXACTLY) {
// only the height is fixed, adjust the width to match aspect ratio if possible
height = heightSpecSize;
width = height * mVideoWidth / mVideoHeight;
if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) {
// couldn't match aspect ratio within the constraints
width = widthSpecSize;
}
} else {
// neither the width nor the height are fixed, try to use actual video size
width = mVideoWidth;
height = mVideoHeight;
if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) {
// too tall, decrease both width and height
height = heightSpecSize;
width = height * mVideoWidth / mVideoHeight;
}
if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) {
// too wide, decrease both width and height
width = widthSpecSize;
height = width * mVideoHeight / mVideoWidth;
}
}
} else {
// no size yet, just adopt the given spec sizes
}
LogUtils.d("doMeasure --end -- 视频宽videowidth : " + width + " 视频高videoHeight: " + height);
mMeasuredWidth = width;
mMeasuredHeight = height;
}
public int getMeasuredWidth() {
LogUtils.d("getMeasuredWidth : " + mMeasuredWidth);
return mMeasuredWidth;
}
public int getMeasuredHeight() {
LogUtils.d("getMeasuredHeight : " + mMeasuredHeight);
return mMeasuredHeight;
}
public void setAspectRatio(int aspectRatio) {
mCurrentAspectRatio = aspectRatio;
}
}

View File

@@ -0,0 +1,14 @@
package com.pukka.videoplayertv;
public class MediaInfo {
public String mMediaPlayerName;
public String mVideoDecoder;
public String mVideoDecoderImpl;
public String mAudioDecoder;
public String mAudioDecoderImpl;
//public IjkMediaMeta mMeta;
}

View File

@@ -0,0 +1,18 @@
package com.pukka.videoplayertv;
/**
* 媒体控制器控制具体操作接口
* */
public interface MediaPlayerControl {
void start();
void pause();
int getDuration();
int getCurrentPosition();
void seekTo(int pos);
boolean isPlaying();
int getBufferPercentage();
boolean canPause();
boolean canSeekBackward();
boolean canSeekForward();
int getCurrentMode();
}

View File

@@ -0,0 +1,398 @@
package com.pukka.videoplayertv;
import android.content.Context;
import android.net.Uri;
import android.view.Surface;
import android.view.SurfaceHolder;
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.Map;
import tv.danmaku.ijk.media.bmiptvplayer.HyMediaPlayer;
import tv.danmaku.ijk.media.bmiptvplayer.IMediaPlayer;
/**
* Author: xjy
* Date: 2022/11/23 14:12
* Description: 软终端播放器
*/
public class SoftMediaPlayer extends AbstractMediaPlayer {
private static final String TAG = "SoftMediaPlayer";
//软终端播放器
private final HyMediaPlayer mHyMediaPlayer;
private final Object mInitLock = new Object();
//是否销毁
private boolean mIsReleased;
/**
*初始化
* */
public SoftMediaPlayer(Context context) {
synchronized (mInitLock) {
mHyMediaPlayer = new HyMediaPlayer(context);
initLister();
}
}
/**
*初始化
* */
public SoftMediaPlayer(Context context, int playerType){
synchronized (mInitLock) {
mHyMediaPlayer = new HyMediaPlayer(context,playerType);
initLister();
}
}
/**
* 设置SurfaceHolder
* */
@Override
public void setDisplay(SurfaceHolder sh) {
if(null !=mHyMediaPlayer) {
if(!mIsReleased) {
mHyMediaPlayer.setDisplay(sh);
}
}
}
/**
* 设置视频源
* */
@Override
public void setDataSource(Context context, Uri uri) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
if(null != mHyMediaPlayer){
mHyMediaPlayer.setDataSource(context,uri);
}
}
/**
* 设置视频源
* */
@Override
public void setDataSource(Context context, Uri uri, Map<String, String> headers) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
if(null != mHyMediaPlayer){
mHyMediaPlayer.setDataSource(context,uri,headers);
}
}
/**
* 设置视频源
* */
@Override
public void setDataSource(FileDescriptor fd) throws IOException, IllegalArgumentException, IllegalStateException {
if(null != mHyMediaPlayer){
mHyMediaPlayer.setDataSource(fd);
}
}
/**
* 设置视频源
* */
@Override
public void setDataSource(String path) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
if(null != mHyMediaPlayer){
mHyMediaPlayer.setDataSource(path);
}
}
/**
* 获取视频源
* */
@Override
public String getDataSource() {
if(null != mHyMediaPlayer){
return mHyMediaPlayer.getDataSource();
}
return null;
}
/**
* 开始准备
* */
@Override
public void prepareAsync() throws IllegalStateException {
if(null != mHyMediaPlayer){
mHyMediaPlayer.prepareAsync();
}
}
/**
* 播放
* */
@Override
public void start() throws IllegalStateException {
if(null != mHyMediaPlayer){
mHyMediaPlayer.start();
}
}
/**
* 停止
* */
@Override
public void stop() throws IllegalStateException {
if(null != mHyMediaPlayer){
mHyMediaPlayer.stop();
}
}
/**
* 暂停
* */
@Override
public void pause() throws IllegalStateException {
if(null != mHyMediaPlayer){
mHyMediaPlayer.pause();
}
}
@Override
public void setScreenOnWhilePlaying(boolean screenOn) {
if(null!= mHyMediaPlayer){
mHyMediaPlayer.setScreenOnWhilePlaying(screenOn);
}
}
@Override
public int getVideoWidth() {
if(null != mHyMediaPlayer) {
return mHyMediaPlayer.getVideoWidth();
}
return 0;
}
@Override
public int getVideoHeight() {
if(null != mHyMediaPlayer) {
return mHyMediaPlayer.getVideoHeight();
}
return 0;
}
/**
* 是否在播放
* */
@Override
public boolean isPlaying() {
if(null != mHyMediaPlayer) {
return mHyMediaPlayer.isPlaying();
}
return false;
}
/**
* 定点播放
* */
@Override
public void seekTo(long msec) throws IllegalStateException {
if(null != mHyMediaPlayer) {
mHyMediaPlayer.seekTo(msec);
}
}
/**
* 当前Position
* */
@Override
public long getCurrentPosition() {
if(null != mHyMediaPlayer){
return mHyMediaPlayer.getCurrentPosition();
}
return 0;
}
/**
* 获取Duration
* */
@Override
public long getDuration() {
if(null != mHyMediaPlayer){
return mHyMediaPlayer.getDuration();
}
return 0;
}
/**
* 销毁播放器
* */
@Override
public void release() {
if(null != mHyMediaPlayer){
mIsReleased = true;
mHyMediaPlayer.release();
resetListeners();
}
}
/**
* 复位
* */
@Override
public void reset() {
if(null != mHyMediaPlayer){
mHyMediaPlayer.reset();
resetListeners();
}
}
@Override
public void setSpeed(float speed) {
}
@Override
public float getSpeed() {
return 0;
}
/**
* 设置声音
* */
@Override
public void setVolume(float leftVolume, float rightVolume) {
if(null != mHyMediaPlayer){
mHyMediaPlayer.setVolume(leftVolume, rightVolume);
}
}
@Override
public int getAudioSessionId() {
if(null != mHyMediaPlayer){
mHyMediaPlayer.getAudioSessionId();
}
return 0;
}
@Override
public MediaInfo getMediaInfo() {
if(null != mHyMediaPlayer){
mHyMediaPlayer.getMediaInfo();
}
return null;
}
public void setLogEnabled(boolean enable) {
if(null != mHyMediaPlayer){
mHyMediaPlayer.setLogEnabled(enable);
}
}
@Override
public boolean isPlayable() {
if(null != mHyMediaPlayer){
return mHyMediaPlayer.isPlayable();
}
return false;
}
@Override
public void setAudioStreamType(int streamtype) {
if(null != mHyMediaPlayer){
mHyMediaPlayer.setAudioStreamType(streamtype);
}
}
@Override
public void setKeepInBackground(boolean keepInBackground) {
if(null != mHyMediaPlayer){
mHyMediaPlayer.setKeepInBackground(keepInBackground);
}
}
@Override
public int getVideoSarNum() {
if(null != mHyMediaPlayer){
return mHyMediaPlayer.getVideoSarNum();
}
return 0;
}
@Override
public int getVideoSarDen() {
if(null != mHyMediaPlayer){
return mHyMediaPlayer.getVideoSarDen();
}
return 0;
}
@Override
public void setWakeMode(Context context, int mode) {
if(null != mHyMediaPlayer){
mHyMediaPlayer.setWakeMode(context,mode);
}
}
@Override
public void setLooping(boolean looping) {
if(null != mHyMediaPlayer){
mHyMediaPlayer.setLooping(looping);
}
}
@Override
public boolean isLooping() {
if(null != mHyMediaPlayer){
return mHyMediaPlayer.isLooping();
}
return false;
}
@Override
public ITrackInfo[] getTrackInfo() {
return null;
}
/**
* 设置Surface
* */
@Override
public void setSurface(Surface surface) {
if(null != mHyMediaPlayer){
mHyMediaPlayer.setSurface(surface);
}
}
@Override
public int getBufferPercentage() {
return 0;
}
/**
* 播放器回调
* onInfo onBufferingUpdate onCompletion onError onPrepared onSeekComplete onVideoSizeChanged
* */
private void initLister(){
if(null == mHyMediaPlayer){
return;
}
mHyMediaPlayer.setOnInfoListener(new IMediaPlayer.OnInfoListener() {
@Override
public boolean onInfo(IMediaPlayer iMediaPlayer, int i, int i1) {
// TODO: 2023/4/2 放开会报错。。不知道为什么
notifyOnInfo(i,i1);
return false;
}
});
mHyMediaPlayer.setOnBufferingUpdateListener(new IMediaPlayer.OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(IMediaPlayer iMediaPlayer, int i) {
notifyOnBufferingUpdate(i);
}
});
mHyMediaPlayer.setOnCompletionListener(new IMediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(IMediaPlayer iMediaPlayer) {
notifyOnCompletion();
}
});
mHyMediaPlayer.setOnErrorListener(new IMediaPlayer.OnErrorListener() {
@Override
public boolean onError(IMediaPlayer iMediaPlayer, int i, int i1) {
notifyOnError(i,i1);
return false;
}
});
mHyMediaPlayer.setOnPreparedListener(new IMediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(IMediaPlayer iMediaPlayer) {
notifyOnPrepared();
}
});
mHyMediaPlayer.setOnSeekCompleteListener(new IMediaPlayer.OnSeekCompleteListener() {
@Override
public void onSeekComplete(IMediaPlayer iMediaPlayer) {
notifyOnSeekComplete();
}
});
mHyMediaPlayer.setOnVideoSizeChangedListener(new IMediaPlayer.OnVideoSizeChangedListener() {
@Override
public void onVideoSizeChanged(IMediaPlayer iMediaPlayer, int i, int i1, int i2, int i3) {
notifyOnVideoSizeChanged(i,i1,i2,i3);
}
});
}
}

View File

@@ -0,0 +1,318 @@
/*
* Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pukka.videoplayertv;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pukka.libcore.utils.LogUtils;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 播放视频的SurfaceView
*/
public class SurfaceRenderView extends SurfaceView implements IRenderView {
private static final String TAG = "VideoViewTv";
//测量帮助类
private MeasureHelper mMeasureHelper;
//SurfaceCallback
private SurfaceCallback mSurfaceCallback;
int sWidth = 0;
int sHeight = 0;
public SurfaceRenderView(Context context) {
super(context);
initView();
}
public SurfaceRenderView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public SurfaceRenderView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SurfaceRenderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initView();
}
private void initView() {
// 实例化measureHelper
mMeasureHelper = new MeasureHelper(this);
// 实例化surfacecallback
mSurfaceCallback = new SurfaceCallback(this);
getHolder().addCallback(mSurfaceCallback);
// noinspection deprecation
getHolder().setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
}
//IRenderView
@Override
public View getView() {
return this;
}
//IRenderView
@Override
public boolean shouldWaitForResize() {
return false;
}
//--------------------
// IRenderView
// Layout & Measure
//--------------------
@Override
public void setVideoSize(int videoWidth, int videoHeight) {
if (videoWidth > 0 && videoHeight > 0) {
LogUtils.d("setVideoSize -- width: " + videoWidth + "--videoHeight : " + videoHeight);
mMeasureHelper.setVideoSize(videoWidth, videoHeight);
getHolder().setFixedSize(videoWidth, videoHeight);
requestLayout();
}
}
@Override
public void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen) {
if (videoSarNum > 0 && videoSarDen > 0) {
mMeasureHelper.setVideoSampleAspectRatio(videoSarNum, videoSarDen);
LogUtils.d("setVideoSampleAspectRatio -- videoSarNum : " + videoSarNum);
requestLayout();
}
}
@Override
public void setVideoRotation(int degree) {
Log.e("", "SurfaceView doesn't support rotation (" + degree + ")!\n");
}
@Override
public void setAspectRatio(int aspectRatio) {
Log.d("wxf", "setAspectRatio - " + aspectRatio);
mMeasureHelper.setAspectRatio(aspectRatio);
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec);
//设置控件的宽和高
setMeasuredDimension(mMeasureHelper.getMeasuredWidth(), mMeasureHelper.getMeasuredHeight());
}
//--------------------
// SurfaceViewHolder
//--------------------
private static final class InternalSurfaceHolder implements IRenderView.ISurfaceHolder {
//播放视频的VIEW
private SurfaceRenderView mSurfaceView;
//播放视频的surfaceHolder
private SurfaceHolder mSurfaceHolder;
public InternalSurfaceHolder(@NonNull SurfaceRenderView surfaceView,
@Nullable SurfaceHolder surfaceHolder) {
mSurfaceView = surfaceView;
mSurfaceHolder = surfaceHolder;
LogUtils.d("InternalSurfaceHolder --mSurfaceHolder-- " + mSurfaceHolder + " -- mSurfaceView :" + mSurfaceView);
}
//surfaceHolder 传递给IMediaPlayer
@Override
public void bindToMediaPlayer(IMediaPlayer mp) {
if (mp != null) {
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) &&
(mp instanceof ISurfaceTextureHolder)) {
ISurfaceTextureHolder textureHolder = (ISurfaceTextureHolder) mp;
textureHolder.setSurfaceTexture(null);
}
LogUtils.d("bindToMediaPlayer --mSurfaceHolder-- " + mSurfaceHolder);
mp.setDisplay(mSurfaceHolder);
}
}
@NonNull
@Override
public IRenderView getRenderView() {
return mSurfaceView;
}
@Nullable
@Override
public SurfaceHolder getSurfaceHolder() {
return mSurfaceHolder;
}
@Nullable
@Override
public SurfaceTexture getSurfaceTexture() {
return null;
}
@Nullable
@Override
public Surface openSurface() {
if (mSurfaceHolder == null) {
return null;
}
return mSurfaceHolder.getSurface();
}
}
//-------------------------
// SurfaceHolder.Callback
//-------------------------
@Override
public void addRenderCallback(IRenderCallback callback) {
mSurfaceCallback.addRenderCallback(callback);
}
@Override
public void removeRenderCallback(IRenderCallback callback) {
mSurfaceCallback.removeRenderCallback(callback);
}
//private SurfaceCallback mSurfaceCallback;
private static final class SurfaceCallback implements SurfaceHolder.Callback {
private SurfaceHolder mSurfaceHolder;
private boolean mIsFormatChanged;
private int mFormat;
private int mWidth;
private int mHeight;
//弱引用 SurfaceRenderView
private WeakReference<SurfaceRenderView> mWeakSurfaceView;
private Map<IRenderCallback, Object> mRenderCallbackMap = new ConcurrentHashMap<IRenderCallback, Object>();
public SurfaceCallback(@NonNull SurfaceRenderView surfaceView) {
mWeakSurfaceView = new WeakReference<SurfaceRenderView>(surfaceView);
}
public void addRenderCallback(@NonNull IRenderCallback callback) {
mRenderCallbackMap.put(callback, callback);
ISurfaceHolder surfaceHolder = null;
if (mSurfaceHolder != null) {
if (surfaceHolder == null) {
surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder);
}
callback.onSurfaceCreated(surfaceHolder, mWidth, mHeight);
}
if (mIsFormatChanged) {
if (surfaceHolder == null) {
surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder);
}
callback.onSurfaceChanged(surfaceHolder, mFormat, mWidth, mHeight);
}
}
public void removeRenderCallback(@NonNull IRenderCallback callback) {
mRenderCallbackMap.remove(callback);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.d(TAG, "surfaceCreated: ");
mSurfaceHolder = holder;
mIsFormatChanged = false;
mFormat = 0;
mWidth = 0;
mHeight = 0;
ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder);
for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) {
renderCallback.onSurfaceCreated(surfaceHolder, 0, 0);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "surfaceDestroyed: ");
mSurfaceHolder = null;
mIsFormatChanged = false;
mFormat = 0;
mWidth = 0;
mHeight = 0;
ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder);
for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) {
renderCallback.onSurfaceDestroyed(surfaceHolder);
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
Log.d(TAG, "surfaceChanged: ");
mSurfaceHolder = holder;
mIsFormatChanged = true;
mFormat = format;
mWidth = width;
mHeight = height;
// mMeasureHelper.setVideoSize(width, height);
ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder);
for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) {
renderCallback.onSurfaceChanged(surfaceHolder, format, width, height);
}
}
}
//--------------------
// Accessibility
//--------------------
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(SurfaceRenderView.class.getName());
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
info.setClassName(SurfaceRenderView.class.getName());
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,170 @@
package com.pukka.videoplayertv;
import android.app.ActionBar;
import android.content.Context;
import android.media.AudioManager;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pukka.mediaplayertv.R;
import java.util.ArrayList;
/**
* Author: wxf
* Date: 2023/4/5 10:00
* Description:点播播控
* */
public class TvMediaController extends TvBaseController implements IMediaController {
private ActionBar mActionBar;
private RelativeLayout mTouchVolumePanel;
private SeekBar mTouchVolumeSeekBar;
private TextView mTouchVolumeValueTv;
private AudioManager mAudioManager;
private boolean mIsTouchMode;
public TvMediaController(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public TvMediaController(Context context, boolean useFastForward) {
super(context, useFastForward);
initView(context);
}
public TvMediaController(Context context) {
super(context);
initView(context);
}
private void initView(Context context) {
mAudioManager = (AudioManager) context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
}
@Override
public void setTouchModeEnabled(boolean isTouchMode) {
mIsTouchMode = isTouchMode;
updateTouchVolumeVisibility();
if (mIsTouchMode) {
syncSystemVolumeToUi();
}
super.setTouchModeEnabled(isTouchMode);
}
private void bindTouchVolumeViews() {
mTouchVolumePanel = findViewById(R.id.touch_volume_panel);
mTouchVolumeSeekBar = findViewById(R.id.sb_touch_volume);
mTouchVolumeValueTv = findViewById(R.id.tv_touch_volume_value);
if (mTouchVolumeSeekBar != null) {
mTouchVolumeSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (!fromUser) {
return;
}
if (mAudioManager != null) {
int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
int targetVolume = (int) ((progress / 100f) * maxVolume);
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, targetVolume, 0);
}
if (mTouchVolumeValueTv != null) {
mTouchVolumeValueTv.setText(String.valueOf(progress));
}
show();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
show();
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
show();
}
});
}
updateTouchVolumeVisibility();
syncSystemVolumeToUi();
}
private void updateTouchVolumeVisibility() {
if (mTouchVolumePanel != null) {
mTouchVolumePanel.setVisibility(mIsTouchMode ? View.VISIBLE : View.GONE);
}
}
private void syncSystemVolumeToUi() {
if (mAudioManager == null || mTouchVolumeSeekBar == null || mTouchVolumeValueTv == null) {
return;
}
int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
int currentVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
mTouchVolumeSeekBar.setMax(100);
int progress = maxVolume > 0 ? (int) ((currentVolume * 100f) / maxVolume) : 0;
mTouchVolumeSeekBar.setProgress(progress);
mTouchVolumeValueTv.setText(String.valueOf(progress));
}
public void setSupportActionBar(@Nullable ActionBar actionBar) {
mActionBar = actionBar;
if (isShowing()) {
//媒体控制器显示,则actionBar显示
actionBar.show();
} else {
actionBar.hide();
}
}
@Override
public void show() {
syncSystemVolumeToUi();
super.show();
//Log.e("wxf", "show 1111");
//显示媒体控制器
if (mActionBar != null)
//显示actionBar
//Log.e("wxf","show 2222");
{
mActionBar.show();
}
}
@Override
public void hide() {
super.hide();
if (mActionBar != null) {
mActionBar.hide();
}
}
@Override
public void setAnchorView(ViewGroup view) {
super.setAnchorView(view);
bindTouchVolumeViews();
}
@Override
public void setMediaPlayer(MediaPlayerControl player) {
super.setMediaPlayer(player);
}
//----------
// Extends
//----------
private ArrayList<View> mShowOnceArray = new ArrayList<View>();
public void showOnce(@NonNull View view) {
mShowOnceArray.add(view);
view.setVisibility(View.VISIBLE);
show();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,169 @@
package com.pukka.videoplayertv.exoplayer;
import android.content.Context;
import android.net.Uri;
import android.text.TextUtils;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.source.rtsp.RtspMediaSource;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.cache.Cache;
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.util.Util;
import java.io.File;
import java.lang.reflect.Field;
import java.util.Map;
public final class ExoMediaSourceHelper {
private static volatile ExoMediaSourceHelper sInstance;
private final String mUserAgent;
private final Context mAppContext;
private HttpDataSource.Factory mHttpDataSourceFactory;
private Cache mCache;
private ExoMediaSourceHelper(Context context) {
mAppContext = context.getApplicationContext();
mUserAgent = Util.getUserAgent(mAppContext, mAppContext.getApplicationInfo().name);
}
public static ExoMediaSourceHelper getInstance(Context context) {
if (sInstance == null) {
synchronized (ExoMediaSourceHelper.class) {
if (sInstance == null) {
sInstance = new ExoMediaSourceHelper(context);
}
}
}
return sInstance;
}
public MediaSource getMediaSource(String uri) {
return getMediaSource(uri, null, false);
}
public MediaSource getMediaSource(String uri, Map<String, String> headers) {
return getMediaSource(uri, headers, false);
}
public MediaSource getMediaSource(String uri, boolean isCache) {
return getMediaSource(uri, null, isCache);
}
public MediaSource getMediaSource(String uri, Map<String, String> headers, boolean isCache) {
Uri contentUri = Uri.parse(uri);
// if ("rtmp".equals(contentUri.getScheme())) {
// return new ProgressiveMediaSource.Factory(new RtmpDataSource.Factory())
// .createMediaSource(MediaItem.fromUri(contentUri));
// } else
if ("rtsp".equals(contentUri.getScheme())) {
return new RtspMediaSource.Factory().createMediaSource(MediaItem.fromUri(contentUri));
}
int contentType = inferContentType(uri);
DataSource.Factory factory;
if (isCache) {
factory = getCacheDataSourceFactory();
} else {
factory = getDataSourceFactory();
}
if (mHttpDataSourceFactory != null) {
setHeaders(headers);
}
switch (contentType) {
case C.CONTENT_TYPE_DASH:
return new DashMediaSource.Factory(factory).createMediaSource(MediaItem.fromUri(contentUri));
case C.CONTENT_TYPE_HLS:
return new HlsMediaSource.Factory(factory).createMediaSource(MediaItem.fromUri(contentUri));
default:
case C.CONTENT_TYPE_OTHER:
return new ProgressiveMediaSource.Factory(factory).createMediaSource(MediaItem.fromUri(contentUri));
}
}
private int inferContentType(String fileName) {
fileName = fileName.toLowerCase();
if (fileName.contains(".mpd")) {
return C.CONTENT_TYPE_DASH;
} else if (fileName.contains(".m3u8")) {
return C.CONTENT_TYPE_HLS;
} else {
return C.CONTENT_TYPE_OTHER;
}
}
private DataSource.Factory getCacheDataSourceFactory() {
if (mCache == null) {
mCache = newCache();
}
return new CacheDataSource.Factory()
.setCache(mCache)
.setUpstreamDataSourceFactory(getDataSourceFactory())
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);
}
private Cache newCache() {
return new SimpleCache(
new File(mAppContext.getExternalCacheDir(), "exo-video-cache"),//缓存目录
new LeastRecentlyUsedCacheEvictor(512 * 1024 * 1024),//缓存大小,默认512M,使用LRU算法实现
new StandaloneDatabaseProvider(mAppContext));
}
/**
* Returns a new DataSource factory.
*
* @return A new DataSource factory.
*/
private DataSource.Factory getDataSourceFactory() {
return new DefaultDataSource.Factory(mAppContext, getHttpDataSourceFactory());
}
/**
* Returns a new HttpDataSource factory.
*
* @return A new HttpDataSource factory.
*/
private DataSource.Factory getHttpDataSourceFactory() {
if (mHttpDataSourceFactory == null) {
mHttpDataSourceFactory = new DefaultHttpDataSource.Factory()
.setUserAgent(mUserAgent)
.setAllowCrossProtocolRedirects(true);
}
return mHttpDataSourceFactory;
}
private void setHeaders(Map<String, String> headers) {
if (headers != null && headers.size() > 0) {
//如果发现用户通过header传递了UA,则强行将HttpDataSourceFactory里面的userAgent字段替换成用户的
if (headers.containsKey("User-Agent")) {
String value = headers.remove("User-Agent");
if (!TextUtils.isEmpty(value)) {
try {
Field userAgentField = mHttpDataSourceFactory.getClass().getDeclaredField("userAgent");
userAgentField.setAccessible(true);
userAgentField.set(mHttpDataSourceFactory, value);
} catch (Exception e) {
//ignore
}
}
}
mHttpDataSourceFactory.setDefaultRequestProperties(headers);
}
}
public void setCache(Cache cache) {
this.mCache = cache;
}
}

View File

@@ -0,0 +1,896 @@
package com.pukka.videoplayertv.exoplayer;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaFormat;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.view.Surface;
import android.view.SurfaceHolder;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.analytics.DefaultAnalyticsCollector;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.EventLogger;
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
import com.google.android.exoplayer2.video.VideoSize;
import com.pukka.libcore.base.BuildConfig;
import com.pukka.libcore.threading.DispatchQueue;
import com.pukka.libcore.utils.LogUtils;
import com.pukka.videoplayertv.AbstractMediaPlayer;
import com.pukka.videoplayertv.IMediaDataSource;
import com.pukka.videoplayertv.ITrackInfo;
import com.pukka.videoplayertv.MediaInfo;
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.Map;
/**
* PkExoMediaPlayer 播放器内核
*/
public final class PkExoMediaPlayer extends AbstractMediaPlayer implements Player.Listener {
private final static String TAG = "PkExoMediaPlayer";
private final static String TAG2 = "PkExoMediaPlayer|播放流程";
/**
* 视频/音频开始渲染
*/
public static final int MEDIA_INFO_RENDERING_START = 3;
/**
* 缓冲开始
*/
public static final int MEDIA_INFO_BUFFERING_START = 701;
/**
* 缓冲结束
*/
public static final int MEDIA_INFO_BUFFERING_END = 702;
protected Context mAppContext;
protected ExoPlayer mInternalPlayer;
protected MediaSource mMediaSource;
protected ExoMediaSourceHelper mMediaSourceHelper;
private PlaybackParameters mSpeedPlaybackParameters;
private boolean mIsPreparing;
private RenderersFactory mRenderersFactory;
private TrackSelector mTrackSelector;
private String playUrl;
private boolean isSeek = false;
private volatile long duration = 0;
private volatile long currentPosition = 0;
private volatile int videoWidth = 0;
private volatile int videoHeight = 0;
private volatile boolean isPlaying = false;
public PkExoMediaPlayer(Context context) {
LogUtils.d(TAG, "播放器版本:" + BuildConfig.VERSION_NAME);
LogUtils.d(TAG2, "创建播放器");
DispatchQueue.main.async(() -> {
LogUtils.d(TAG, "starting:3");
playUrl = "";
isFirst_BUFFERING_START = true;
mAppContext = context.getApplicationContext();
mMediaSourceHelper = ExoMediaSourceHelper.getInstance(context);
this.mInternalPlayer = initPlayer();
mInternalPlayer.setWakeMode(C.WAKE_MODE_LOCAL); // 保持屏幕常亮
setOptions();
initMediaListener();
});
}
public ExoPlayer initPlayer() {
// // 创建 DataSource 工厂
// DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory();
//
// // 创建 ExoPlayer 实例
// ExoPlayer mInternalPlayer = new ExoPlayer.Builder(mAppContext)
// .setMediaSourceFactory(new DefaultMediaSourceFactory(dataSourceFactory)) // 默认支持多种格式
// .build();
// 增加缓冲区大小
DefaultLoadControl mLoadControl = new DefaultLoadControl.Builder()
.setBufferDurationsMs(50000, 50000, 1500, 2000)
.build();
ExoPlayer mInternalPlayer = new ExoPlayer.Builder(
mAppContext,
mRenderersFactory == null ? mRenderersFactory = new DefaultRenderersFactory(mAppContext).setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER) : mRenderersFactory,
new DefaultMediaSourceFactory(mAppContext),
mTrackSelector == null ? mTrackSelector = new DefaultTrackSelector(mAppContext) : mTrackSelector,
mLoadControl,
DefaultBandwidthMeter.getSingletonInstance(mAppContext),
new DefaultAnalyticsCollector(Clock.DEFAULT))
.build();
mInternalPlayer.setVideoFrameMetadataListener(new VideoFrameMetadataListener() {
@Override
public void onVideoFrameAboutToBeRendered(long presentationTimeUs, long releaseTimeNs, Format format, MediaFormat mediaFormat) {
// 每帧渲染前的回调
// LogUtils.d("播放帧", "presentationTimeUs: " + presentationTimeUs + " releaseTimeNs:"+releaseTimeNs);
if (isVideoFrameMetadata){
isVideoFrameMetadata = false;
LogUtils.d("播放帧", "实际播放第二帧");
}
}
});
mInternalPlayer.addAnalyticsListener(new EventLogger());
return mInternalPlayer;
}
public void setOptions() {
LogUtils.d(TAG, "setOptions");
//准备好了,先不播放,手动调才能播放
if (null != mInternalPlayer) {
mInternalPlayer.setPlayWhenReady(false);
}
// 检查 FFmpeg 扩展库是否加载
// if (FfmpegLibrary.isAvailable()) {
// LogUtils.d("FFmpeg extension is available");
// } else {
// LogUtils.d("FFmpeg extension is NOT available");
// }
//放着音乐去视频播放,两个声音
LogUtils.d(TAG, "AudioManager init");
AudioManager audioManager = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(int focusChange) {
// 处理音频焦点变化
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
LogUtils.d(TAG, "AudioManager AUDIOFOCUS_GAIN");
// 重新获得焦点,恢复播放
break;
case AudioManager.AUDIOFOCUS_LOSS:
LogUtils.d(TAG, "AudioManager AUDIOFOCUS_LOSS");
// 失去焦点,暂停播放
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// 暂时失去焦点,暂停播放
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// 暂时失去焦点,降低音量
break;
}
}
};
LogUtils.d(TAG, "AudioManager requestAudioFocus()");
int result = audioManager.requestAudioFocus(
audioFocusChangeListener,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN
);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// 成功获得焦点,开始播放
LogUtils.d(TAG, "AudioManager get focus success");
} else {
// 未获得焦点,处理失败情况
LogUtils.d(TAG, "AudioManager luss focus");
}
}
/**
* 倍速播放
*
* @param speed 倍速 2f
*/
@Override
public void setSpeed(float speed) {
LogUtils.d(TAG, "setSpeed: " + speed);
DispatchQueue.main.async(() -> {
if (mInternalPlayer == null) {
LogUtils.d(TAG, "ExoPlayer == null");
return;
}
PlaybackParameters playbackParameters = new PlaybackParameters(speed);
mSpeedPlaybackParameters = playbackParameters;
if (mInternalPlayer != null) {
mInternalPlayer.setPlaybackParameters(playbackParameters);
}
});
}
@Override
public float getSpeed() {
if (mInternalPlayer==null){
return 1;
}
return mInternalPlayer.getPlaybackParameters().speed;
}
@Override
public void setDisplay(SurfaceHolder holder) {
LogUtils.d(TAG, "setDisplay");
DispatchQueue.main.async(() -> {
LogUtils.d(TAG, "statue setDisplay:" + holder);
if (mInternalPlayer == null) {
LogUtils.d(TAG, "mInternalPlayer == null");
return;
}
if (holder == null)
setSurface(null);
else
setSurface(holder.getSurface());
});
}
@Override
public void setSurface(Surface surface) {
LogUtils.d(TAG, "setSurface");
DispatchQueue.main.async(() -> {
if (mInternalPlayer == null) {
LogUtils.d(TAG, "iMediaPlayer == null");
return;
}
LogUtils.d(TAG, "statue setSurface()");
if (mInternalPlayer != null) {
mInternalPlayer.setVideoSurface(surface);
}
});
}
@Override
public void setDataSource(IMediaDataSource mediaDataSource) {
}
@Override
public void setDataSource(Context var1, Uri var2) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
LogUtils.d(TAG, "setDataSource 2:" + var2.getPath());
LogUtils.d(TAG2, "设置播放地址:" + var2.getPath());
DispatchQueue.main.async(() -> {
LogUtils.d(TAG, "setDataSource Uri:" + var2);
if (mInternalPlayer == null) {
LogUtils.d(TAG, "ExoPlayer == null");
return;
}
initSeekTime();
if (null != var2) {
String path = var2.toString();
playUrl = path;
mMediaSource = mMediaSourceHelper.getMediaSource(path, null);
LogUtils.d("setDataSource () statue starting analysis playUrl:" + playUrl);
}
});
}
@Override
public void setDataSource(Context var1, Uri var2, Map<String, String> var3) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
LogUtils.d(TAG, "setDataSource 3:" + var2.toString());
LogUtils.d(TAG2, "设置播放地址:" + var2.toString());
DispatchQueue.main.async(() -> {
if (mInternalPlayer == null) {
LogUtils.d(TAG, "mInternalPlayer == null");
return;
}
initSeekTime();
if (null != var2) {
String path = var2.toString();
playUrl = path;
mMediaSource = mMediaSourceHelper.getMediaSource(path, var3);
LogUtils.d("setDataSource () statue starting analysis play");
}
});
}
@Override
public void setDataSource(FileDescriptor var1) throws IOException, IllegalArgumentException, IllegalStateException {
//no support
LogUtils.d(TAG, "setDataSource 4");
}
@Override
public void setDataSource(String var1) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
LogUtils.d(TAG, "setDataSource 5:" + var1);
LogUtils.d(TAG2, "设置播放地址:" + var1);
DispatchQueue.main.async(() -> {
if (mInternalPlayer == null) {
LogUtils.d(TAG, "mInternalPlayer == null");
return;
}
initSeekTime();
playUrl = var1;
mMediaSource = mMediaSourceHelper.getMediaSource(var1, null);
LogUtils.d("statue setDataSource() starting analysis playUrl:" + playUrl);
});
}
@Override
public String getDataSource() {
LogUtils.d(TAG, "getDataSource");
if (mInternalPlayer == null) {
LogUtils.d(TAG, "mInternalPlayer == null");
return null;
}
return playUrl;
}
@Override
public void prepareAsync() throws IllegalStateException {
LogUtils.d(TAG, "prepareAsync");
LogUtils.d(TAG2, "开始解码准备播放");
DispatchQueue.main.async(() -> {
if (mInternalPlayer == null) {
LogUtils.d(TAG, "mInternalPlayer == null");
return;
}
if (mInternalPlayer == null)
return;
if (mMediaSource == null) return;
if (mSpeedPlaybackParameters != null) {
mInternalPlayer.setPlaybackParameters(mSpeedPlaybackParameters);
}
mIsPreparing = true;
mInternalPlayer.setMediaSource(mMediaSource);
mInternalPlayer.setPlayWhenReady(false);
mInternalPlayer.prepare();
});
}
@Override
public void start() throws IllegalStateException {
LogUtils.d(TAG, "start");
LogUtils.d(TAG2, "开始缓冲");
DispatchQueue.main.async(() -> {
LogUtils.d(TAG, "statue starting analysis start()");
if (mInternalPlayer == null) {
LogUtils.d(TAG, "mInternalPlayer == null");
return;
}
mInternalPlayer.setPlayWhenReady(true);
});
}
@Override
public void stop() throws IllegalStateException {
LogUtils.d(TAG, "stop");
LogUtils.d(TAG2, "停止播放");
DispatchQueue.main.async(() -> {
LogUtils.d(TAG, "statue stop:");
if (mInternalPlayer == null) {
LogUtils.d(TAG, "mInternalPlayer == null");
return;
}
mInternalPlayer.stop();
});
}
@Override
public void pause() throws IllegalStateException {
LogUtils.d(TAG, "pause");
LogUtils.d(TAG2, "暂停播放");
DispatchQueue.main.async(() -> {
LogUtils.d(TAG, "statue pause:");
if (mInternalPlayer == null) {
LogUtils.d(TAG, "mInternalPlayer == null");
return;
}
mInternalPlayer.setPlayWhenReady(false);
});
}
@Override
public void setScreenOnWhilePlaying(boolean var1) {
LogUtils.d(TAG, "setScreenOnWhilePlaying");
//no support
}
@Override
public int getVideoWidth() {
LogUtils.d(TAG, "getVideoWidth");
//no support
//电视看点
if (videoWidth == 0) {
videoWidth = 1920;
}
LogUtils.d(TAG, "getVideoWidth videoWidth:" + videoWidth);
return videoWidth;
}
@Override
public int getVideoHeight() {
LogUtils.d(TAG, "getVideoHeight");
//no support
if (videoHeight == 0) {
videoHeight = 1080;
}
LogUtils.d(TAG, "getVideoHeight videoHeight:" + videoHeight);
return videoHeight;
}
@Override
public boolean isPlaying() {
// LogUtils.d(TAG, "isPlaying");
if (isMainThread()) {
if (mInternalPlayer == null) {
return false;
} else {
int state = mInternalPlayer.getPlaybackState();
if (state == Player.STATE_BUFFERING && isSeek) {
LogUtils.d(TAG, "isPlaying seekTo: main thread isPlaying():=isSeek" + isSeek);
return true;
}
isPlaying = mInternalPlayer.isPlaying();
// LogUtils.d(TAG, "isPlaying main thread isPlaying():==" + isPlaying);
return mInternalPlayer.isPlaying();
}
} else {
DispatchQueue.main.async(() -> {
if (mInternalPlayer == null) {
LogUtils.d(TAG, "isPlaying mInternalPlayer == null");
isPlaying = false;
} else {
isPlaying = mInternalPlayer.isPlaying();
LogUtils.d(TAG, "isPlaying isPlaying:33 :" + isPlaying);
}
});
try {
//20比较合适
Thread.sleep(20);
} catch (Exception ex) {
ex.printStackTrace();
}
}
LogUtils.d(TAG, "isPlaying 4:" + isPlaying);
return isPlaying;
}
@Override
public void seekTo(long time) throws IllegalStateException {
LogUtils.d(TAG, "seekTo:" + time);
LogUtils.d(TAG2, "seekTo播放:" + time);
isSeek = true;
DispatchQueue.main.async(() -> {
LogUtils.d(TAG, "seekTo statue starting analysis seekTo:" + time);
if (mInternalPlayer == null) {
LogUtils.d(TAG, "iMediaPlayer == null");
return;
}
if (time < 0) {
LogUtils.d(TAG, "seekTo value =" + time);
return;
}
mInternalPlayer.seekTo(time);
});
}
@Override
public long getCurrentPosition() {
LogUtils.d(TAG, "getCurrentPosition");
if (isMainThread()) {
if (mInternalPlayer == null) {
LogUtils.d(TAG, "iMediaPlayer == null");
currentPosition = 0;
return 0;
}
currentPosition = mInternalPlayer.getCurrentPosition();
LogUtils.d(TAG, "getCurrentPosition:" + mInternalPlayer.getCurrentPosition());
return currentPosition;
}
return currentPosition;
}
@Override
public long getDuration() {
LogUtils.d(TAG, "getDuration");
if (isMainThread()) {
if (mInternalPlayer == null) {
LogUtils.d(TAG, "mInternalPlayer == null");
duration = 0;
} else {
duration = mInternalPlayer.getDuration();
LogUtils.d(TAG, "main Thread getDuration:11 :" + duration);
return duration;
}
}
return duration;
}
public boolean isMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
@Override
public void setVolume(float leftVolume, float rightVolume) {
LogUtils.d(TAG, "setVolume:" + leftVolume + " " + rightVolume);
DispatchQueue.main.async(() -> {
if (mInternalPlayer == null) {
LogUtils.d(TAG, "mInternalPlayer == null");
return;
}
LogUtils.d(TAG, "setVolume leftVolume:" + leftVolume + "|| rightVolume:" + rightVolume);
if (mInternalPlayer != null)
mInternalPlayer.setVolume((leftVolume + rightVolume) / 2);
});
}
@Override
public int getAudioSessionId() {
LogUtils.d(TAG, "getAudioSessionId");
if (mInternalPlayer == null) {
LogUtils.d(TAG, "iMediaPlayer == null");
return 0;
}
return mInternalPlayer.getAudioSessionId();
}
@Override
public MediaInfo getMediaInfo() {
return null;
}
@Override
public boolean isPlayable() {
LogUtils.d(TAG, "isPlayable");
//no support
return true;
}
private boolean isFirst_BUFFERING_START = true;
private boolean isVideoFrameMetadata = false;
/***
* ExoMediaPlayer播放相关回调
* */
@Override
public void onPlaybackStateChanged(int playbackState) {
LogUtils.d(TAG, "onPlaybackStateChanged");
LogUtils.d(TAG, "onPlaybackStateChanged infoListener.onInfo mIsPreparing:" + mIsPreparing + " isSeek:" + isSeek);
LogUtils.d(TAG, "onPlaybackStateChanged infoListener.onInfo:" + playbackState);
if (mIsPreparing) {
if (playbackState == Player.STATE_READY) {
LogUtils.d(TAG, "onPlaybackStateChanged starting analysis exoPlayer statue: onPrepared");
notifyOnPrepared();
if (!isSeek) {
notifyOnInfo(MEDIA_INFO_RENDERING_START, 0);
}
mIsPreparing = false;
LogUtils.d(TAG2, "缓冲完毕开始展示播放画面1:" + isSeek);
} else if (playbackState == Player.STATE_BUFFERING) {
LogUtils.d(TAG2, "解码完毕准备缓冲");
} else if (playbackState == Player.STATE_ENDED) {
LogUtils.d(TAG2, "播放器缓冲结束");
} else if (playbackState == Player.STATE_IDLE) {
}
} else {
switch (playbackState) {
case Player.STATE_BUFFERING:
LogUtils.d(TAG2, "正在缓冲");
LogUtils.d(TAG, "onPlaybackStateChanged starting analysis infoListener.onInfo:" + MEDIA_INFO_BUFFERING_START + " percentage:" + getBufferPercentage());
notifyOnInfo(MEDIA_INFO_BUFFERING_START, 0);
break;
case Player.STATE_READY:
//缓冲结束 缓冲结束,可以开始播放
if (isFirst_BUFFERING_START) {
notifyOnInfo(MEDIA_INFO_BUFFERING_START, 0);
}
notifyOnInfo(MEDIA_INFO_BUFFERING_START, 0);
isVideoFrameMetadata = true;
notifyOnInfo(MEDIA_INFO_RENDERING_START, 0);
LogUtils.d(TAG2, "缓冲完毕开始展示播放画面2:"+ isSeek);
LogUtils.d(TAG, "onPlaybackStateChanged starting analysis infoListener.onInfo: " + MEDIA_INFO_BUFFERING_END);
break;
case Player.STATE_ENDED:
LogUtils.d(TAG2, "播放器缓冲结束");
notifyOnCompletion();
LogUtils.d(TAG2, "播放器准备完毕");
LogUtils.d(TAG, "onPlaybackStateChanged completionListener.onCompletion:");
initSeekTime();
break;
case Player.STATE_IDLE:
break;
}
}
}
@Override
public void onPlayerError(PlaybackException error) {
LogUtils.d(TAG, "onPlayerError");
String errorMessage = "code:" + error.errorCode + " ||message:" + error.getMessage();
LogUtils.d(TAG2, "播放器报错 " + errorMessage);
initSeekTime();
notifyOnError(error.errorCode, errorMessage.contains("Format")?264:9999);
}
@Override
public void onRenderedFirstFrame() {
LogUtils.d(TAG, "onRenderedFirstFrame");
}
@Override
public void onPositionDiscontinuity(Player.PositionInfo oldPosition, Player.PositionInfo newPosition, int reason) {
LogUtils.d(TAG, "onPositionDiscontinuity");
isSeek = false;
DispatchQueue.main.async(() -> {
//doSeekComplete(mInternalPlayer);
// Seek 操作完成
// if (null != seekCompleteListener) {
// LogUtils.d(TAG, "onPositionDiscontinuity seekCompleteListener() oldPosition:" + oldPosition.positionMs + " newPosition:" + newPosition.positionMs);
// //seekCompleteListener.onSeekComplete(mInternalPlayer);
// }
LogUtils.d(TAG, "onPositionDiscontinuity seekCompleteListener() oldPosition:" + oldPosition.positionMs + " newPosition:" + newPosition.positionMs);
notifyOnSeekComplete();
});
}
@Override
public void onVideoSizeChanged(VideoSize videoSize) {
LogUtils.d(TAG, "onVideoSizeChanged");
videoWidth = videoSize.width;
videoHeight = videoSize.height;
LogUtils.d(TAG, "onVideoSizeChanged videoWidth:" + videoWidth + " videoHeight:" + videoHeight);
notifyOnVideoSizeChanged(videoSize.width, videoSize.height, 0, 0);
}
// int MEDIA_INFO_UNKNOWN = 1;//未知信息
// int MEDIA_INFO_STARTED_AS_NEXT = 2;//播放下一条
// int MEDIA_INFO_VIDEO_RENDERING_START = 3;//视频开始整备中,准备渲染
// int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700;//视频日志跟踪
// int MEDIA_INFO_BUFFERING_START = 701;//开始缓冲中 开始缓冲
// int MEDIA_INFO_BUFFERING_END = 702;//缓冲结束
// int MEDIA_INFO_NETWORK_BANDWIDTH = 703;//网络带宽,网速方面
// int MEDIA_INFO_BAD_INTERLEAVING = 800;//
// int MEDIA_INFO_NOT_SEEKABLE = 801;//不可设置播放位置,直播方面
// int MEDIA_INFO_METADATA_UPDATE = 802;//
// int MEDIA_INFO_TIMED_TEXT_ERROR = 900;
// int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901;//不支持字幕
// int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902;//字幕超时
// int MEDIA_INFO_VIDEO_INTERRUPT= -10000;//数据连接中断,一般是视频源有问题或者数据格式不支持,比如音频不是AAC之类的
// int MEDIA_INFO_VIDEO_ROTATION_CHANGED = 10001;//视频方向改变,视频选择信息
// int MEDIA_INFO_AUDIO_RENDERING_START = 10002;//音频开始整备中
// int MEDIA_ERROR_SERVER_DIED = 100;//服务挂掉,视频中断,一般是视频源异常或者不支持的视频类型。
// int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200;//数据错误没有有效的回收
// int MEDIA_ERROR_IO = -1004;//IO 错误
// int MEDIA_ERROR_MALFORMED = -1007;
// int MEDIA_ERROR_UNSUPPORTED = -1010;//数据不支持
// int MEDIA_ERROR_TIMED_OUT = -110;//数据超时
//
// int MEDIA_INFO_VIDEO_RENDERING_START = 3;//视频准备渲染
// int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200;//数据错误没有有效的回收
// int MEDIA_INFO_BUFFERING_START = 701;//开始缓冲
// int MEDIA_INFO_BUFFERING_END = 702;//缓冲结束
// int MEDIA_INFO_VIDEO_ROTATION_CHANGED = 10001;//视频选择信息
// int MEDIA_ERROR_SERVER_DIED = 100;//视频中断,一般是视频源异常或者不支持的视频类型。
// int MEDIA_ERROR_IJK_PLAYER = -10000,//一般是视频源有问题或者数据格式不支持,比如音频不是AAC之类的
// @Override
// public void setOnErrorListener(OnErrorListener var1) {
// LogUtils.d(TAG, "setOnErrorListener");
// //错误信息不返回
// DispatchQueue.main.async(() -> {
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return;
// }
// errorListener = var1;
// });
// }
/**
* int MEDIA INFO VIDEO RENDERING START = 3;//视频准备渲染
* int MEDIA INFO BUFFERING START = 701;//开始缓冲
* int MEDIA INFO BUFFERING END = 702;//缓冲结束
* int MEDIA INFO VIDEO ROTATION CHANGED = 10001;//视频选择信息
* int MEDIA ERROR SERVER DIED = 100;//视频中断,一般是视频源异常或者不支持的视频类型。
* int MEDIA ERROR IJK PLAYER = -10800,//一般是视频源有问题或者数据格式不支持,比如音频不是AAC之类的
* int MEDIA ERROR NOT VALID FOR PROGRESSIVE PLAYBACK = 200;//数据错误没有有效的回收
*/
// @Override
// public void setOnInfoListener(OnInfoListener var1) {
// LogUtils.d(TAG, "setOnInfoListener");
// DispatchQueue.main.async(() -> {
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return;
// }
// infoListener = var1;
// });
// }
@Override
public void setAudioStreamType(int var1) {
LogUtils.d(TAG, "setAudioStreamType");
if (mInternalPlayer == null) {
LogUtils.d(TAG, "mInternalPlayer == null");
return;
}
//mInternalPlayer.setAudioStreamType(var1);
//no support
}
@Override
public void setKeepInBackground(boolean var1) {
LogUtils.d(TAG, "setKeepInBackground");
if (mInternalPlayer == null) {
LogUtils.d(TAG, "mInternalPlayer == null");
}
//mInternalPlayer.set
//no support
}
@Override
public int getVideoSarNum() {
LogUtils.d(TAG, "getVideoSarNum");
if (mInternalPlayer == null) {
LogUtils.d(TAG, "mInternalPlayer == null");
return 0;
}
//no support
return 0;
}
@Override
public int getVideoSarDen() {
LogUtils.d(TAG, "getVideoSarDen");
if (mInternalPlayer == null) {
LogUtils.d(TAG, "mInternalPlayer == null");
return 0;
}
//no support
return 0;
}
@Override
public int getBufferPercentage() {
LogUtils.d(TAG, "getBufferPercentage:" + mInternalPlayer.getBufferedPercentage());
return mInternalPlayer == null ? 0 : mInternalPlayer.getBufferedPercentage();
}
@Override
public void setWakeMode(Context var1, int var2) {
LogUtils.d(TAG, "setWakeMode");
DispatchQueue.main.async(() -> {
if (mInternalPlayer == null) {
LogUtils.d(TAG, "mInternalPlayer == null");
return;
}
mInternalPlayer.setWakeMode(var2);
});
}
@Override
public void setLooping(boolean isLooping) {
LogUtils.d(TAG, "setLooping:" + isLooping);
DispatchQueue.main.async(() -> {
if (mInternalPlayer == null) {
LogUtils.d(TAG, "mInternalPlayer == null");
return;
}
if (mInternalPlayer != null)
mInternalPlayer.setRepeatMode(isLooping ? Player.REPEAT_MODE_ALL : Player.REPEAT_MODE_OFF);
});
}
@Override
public boolean isLooping() {
LogUtils.d(TAG, "isLooping");
if (mInternalPlayer == null) {
LogUtils.d(TAG, "iMediaPlayer == null");
return false;
}
if (mInternalPlayer.getRepeatMode() == Player.REPEAT_MODE_ALL) {
return true;
}
return false;
}
@Override
public ITrackInfo[] getTrackInfo() {
return new ITrackInfo[0];
}
private final Handler mainHandler = new Handler(Looper.getMainLooper());
@Override
public void release() {
LogUtils.d(TAG, "release:");
LogUtils.d(TAG2, "释放播放器资源");
mainHandler.post(new Runnable() {
@Override
public void run() {
LogUtils.d(TAG, "statue release");
if (mInternalPlayer == null) {
LogUtils.d(TAG, "mInternalPlayer == null");
return;
}
initSeekTime();
if (mInternalPlayer != null) {
mInternalPlayer.removeListener(PkExoMediaPlayer.this);
mInternalPlayer.release();
mInternalPlayer = null;
}
mIsPreparing = false;
mSpeedPlaybackParameters = null;
}
});
// DispatchQueue.main.async(() -> {
// LogUtils.d(TAG, "statue release");
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return;
// }
// addPlayRecord();
// initSeekTime();
// if (mInternalPlayer != null) {
// mInternalPlayer.removeListener(this);
// mInternalPlayer.release();
// mInternalPlayer = null;
// }
// mIsPreparing = false;
// mSpeedPlaybackParameters = null;
// });
}
@Override
public void reset() {
LogUtils.d(TAG, "reset");
LogUtils.d(TAG2, "重置播放器资源:");
DispatchQueue.main.async(() -> {
LogUtils.d(TAG, "reset start");
if (mInternalPlayer == null) {
LogUtils.d(TAG, "mInternalPlayer == null");
return;
}
initSeekTime();
if (mInternalPlayer != null) {
mInternalPlayer.stop();
mInternalPlayer.clearMediaItems();
mIsPreparing = false;
playUrl = "";
}
LogUtils.d(TAG, "reset end");
});
}
private void initMediaListener() {
LogUtils.d(TAG, "initMediaListener");
mInternalPlayer.addListener(this);
}
/**
* 恢复默认值
*/
private void initSeekTime() {
LogUtils.d(TAG, "initSeekTime");
DispatchQueue.main.async(() -> {
isSeek = false;
duration = 0;
currentPosition = 0;
videoWidth = 0;
videoHeight = 0;
});
}
@Override
public void onTimelineChanged(Timeline timeline, int reason) {
LogUtils.d(TAG, "onTimelineChanged");
Player.Listener.super.onTimelineChanged(timeline, reason);
}
public void resetListeners(){
LogUtils.d(TAG,"resetListeners");
super.resetListeners();
}
}

View File

@@ -0,0 +1,889 @@
//package com.pukka.videoplayertv.exoplayer;
//
//import android.content.Context;
//import android.media.AudioManager;
//import android.media.MediaFormat;
//import android.net.Uri;
//import android.os.Handler;
//import android.os.Looper;
//import android.view.Surface;
//import android.view.SurfaceHolder;
//
//import androidx.media3.common.C;
//import androidx.media3.common.Format;
//import androidx.media3.common.MediaItem;
//import androidx.media3.common.PlaybackException;
//import androidx.media3.common.PlaybackParameters;
//import androidx.media3.common.Player;
//import androidx.media3.common.Timeline;
//import androidx.media3.common.VideoSize;
//import androidx.media3.common.util.UnstableApi;
//import androidx.media3.datasource.DataSource;
//import androidx.media3.datasource.DefaultHttpDataSource;
//import androidx.media3.exoplayer.ExoPlayer;
//import androidx.media3.exoplayer.RenderersFactory;
//import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
//import androidx.media3.exoplayer.trackselection.TrackSelector;
//import androidx.media3.exoplayer.util.EventLogger;
//import androidx.media3.exoplayer.video.VideoFrameMetadataListener;
//
//import com.pukka.libcore.base.BuildConfig;
//import com.pukka.libcore.threading.DispatchQueue;
//import com.pukka.libcore.utils.LogUtils;
//import com.pukka.videoplayertv.AbstractMediaPlayer;
//import com.pukka.videoplayertv.IMediaDataSource;
//import com.pukka.videoplayertv.ITrackInfo;
//import com.pukka.videoplayertv.MediaInfo;
//
//import java.io.FileDescriptor;
//import java.io.IOException;
//import java.util.Map;
//
//
///**
// * PkExoMediaPlayer 播放器内核
// */
//@UnstableApi
//public final class PkHighExoMediaPlayer extends AbstractMediaPlayer implements Player.Listener {
// private final static String TAG = "PkExoMediaPlayer";
// private final static String TAG2 = "PkExoMediaPlayer|播放流程";
// /**
// * 视频/音频开始渲染
// */
// public static final int MEDIA_INFO_RENDERING_START = 3;
// /**
// * 缓冲开始
// */
// public static final int MEDIA_INFO_BUFFERING_START = 701;
// /**
// * 缓冲结束
// */
// public static final int MEDIA_INFO_BUFFERING_END = 702;
// protected Context mAppContext;
// protected ExoPlayer mInternalPlayer;
// protected MediaItem mMediaItem;
// private PlaybackParameters mSpeedPlaybackParameters;
// private boolean mIsPreparing;
// private RenderersFactory mRenderersFactory;
// private TrackSelector mTrackSelector;
// private String playUrl;
// private boolean isSeek = false;
// private volatile long duration = 0;
// private volatile long currentPosition = 0;
// private volatile int videoWidth = 0;
// private volatile int videoHeight = 0;
// private volatile boolean isPlaying = false;
//
// @UnstableApi
// public PkHighExoMediaPlayer(Context context) {
// LogUtils.d(TAG, "播放器版本:" + BuildConfig.VERSION_NAME);
// LogUtils.d(TAG2, "创建播放器");
// DispatchQueue.main.async(() -> {
// LogUtils.d(TAG, "starting:3");
// playUrl = "";
// isFirst_BUFFERING_START = true;
// mAppContext = context.getApplicationContext();
// this.mInternalPlayer = initPlayer();
// mInternalPlayer.setWakeMode(C.WAKE_MODE_LOCAL); // 保持屏幕常亮
// setOptions();
// initMediaListener();
// });
// }
//
// public ExoPlayer initPlayer() {
//
// // 创建 DataSource 工厂
// DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory();
//
// // 创建 ExoPlayer 实例
// ExoPlayer mInternalPlayer = new ExoPlayer.Builder(mAppContext)
// .setMediaSourceFactory(new DefaultMediaSourceFactory(dataSourceFactory)) // 默认支持多种格式
// .build();
//
// // 增加缓冲区大小
//// DefaultLoadControl mLoadControl = new DefaultLoadControl.Builder()
//// .setBufferDurationsMs(50000, 50000, 1500, 2000)
//// .build();
//// ExoPlayer mInternalPlayer = new ExoPlayer.Builder(
//// mAppContext,
//// mRenderersFactory == null ? mRenderersFactory = new DefaultRenderersFactory(mAppContext).setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER) : mRenderersFactory,
//// new DefaultMediaSourceFactory(mAppContext),
//// mTrackSelector == null ? mTrackSelector = new DefaultTrackSelector(mAppContext) : mTrackSelector,
//// mLoadControl,
//// DefaultBandwidthMeter.getSingletonInstance(mAppContext),
//// new DefaultAnalyticsCollector(Clock.DEFAULT))
//// .build();
// mInternalPlayer.setVideoFrameMetadataListener(new VideoFrameMetadataListener() {
// @Override
// public void onVideoFrameAboutToBeRendered(long presentationTimeUs, long releaseTimeNs, Format format, MediaFormat mediaFormat) {
// // 每帧渲染前的回调
//// LogUtils.d("播放帧", "presentationTimeUs: " + presentationTimeUs + " releaseTimeNs:"+releaseTimeNs);
// if (isVideoFrameMetadata) {
// isVideoFrameMetadata = false;
// LogUtils.d("播放帧", "实际播放第二帧");
// }
// }
// });
// mInternalPlayer.addAnalyticsListener(new EventLogger());
// return mInternalPlayer;
// }
//
// public void setOptions() {
// LogUtils.d(TAG, "setOptions");
// //准备好了,先不播放,手动调才能播放
// if (null != mInternalPlayer) {
// mInternalPlayer.setPlayWhenReady(false);
// }
// // 检查 FFmpeg 扩展库是否加载
//// if (FfmpegLibrary.isAvailable()) {
//// LogUtils.d("FFmpeg extension is available");
//// } else {
//// LogUtils.d("FFmpeg extension is NOT available");
//// }
// //放着音乐去视频播放,两个声音
// LogUtils.d(TAG, "AudioManager init");
// AudioManager audioManager = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
// AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
// @Override
// public void onAudioFocusChange(int focusChange) {
// // 处理音频焦点变化
// switch (focusChange) {
// case AudioManager.AUDIOFOCUS_GAIN:
// LogUtils.d(TAG, "AudioManager AUDIOFOCUS_GAIN");
// // 重新获得焦点,恢复播放
// break;
// case AudioManager.AUDIOFOCUS_LOSS:
// LogUtils.d(TAG, "AudioManager AUDIOFOCUS_LOSS");
// // 失去焦点,暂停播放
// break;
// case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// // 暂时失去焦点,暂停播放
// break;
// case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// // 暂时失去焦点,降低音量
// break;
// }
// }
// };
// LogUtils.d(TAG, "AudioManager requestAudioFocus()");
// int result = audioManager.requestAudioFocus(
// audioFocusChangeListener,
// AudioManager.STREAM_MUSIC,
// AudioManager.AUDIOFOCUS_GAIN
// );
// if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// // 成功获得焦点,开始播放
// LogUtils.d(TAG, "AudioManager get focus success");
// } else {
// // 未获得焦点,处理失败情况
// LogUtils.d(TAG, "AudioManager luss focus");
// }
// }
//
// /**
// * 倍速播放
// *
// * @param speed 倍速 2f
// */
// @Override
// public void setSpeed(float speed) {
// LogUtils.d(TAG, "setSpeed: " + speed);
// DispatchQueue.main.async(() -> {
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "ExoPlayer == null");
// return;
// }
// PlaybackParameters playbackParameters = new PlaybackParameters(speed);
// mSpeedPlaybackParameters = playbackParameters;
// if (mInternalPlayer != null) {
// mInternalPlayer.setPlaybackParameters(playbackParameters);
// }
// });
// }
//
// @Override
// public float getSpeed() {
// if (mInternalPlayer==null){
// return 1;
// }
// return mInternalPlayer.getPlaybackParameters().speed;
// }
//
//
// @Override
// public void setDisplay(SurfaceHolder holder) {
// LogUtils.d(TAG, "setDisplay");
// DispatchQueue.main.async(() -> {
// LogUtils.d(TAG, "statue setDisplay:" + holder);
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return;
// }
// if (holder == null)
// setSurface(null);
// else
// setSurface(holder.getSurface());
// });
// }
//
// @Override
// public void setSurface(Surface surface) {
// LogUtils.d(TAG, "setSurface");
// DispatchQueue.main.async(() -> {
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "iMediaPlayer == null");
// return;
// }
// LogUtils.d(TAG, "statue setSurface()");
// if (mInternalPlayer != null) {
// mInternalPlayer.setVideoSurface(surface);
// }
// });
// }
//
// @Override
// public void setDataSource(IMediaDataSource mediaDataSource) {
//
// }
//
// @Override
// public void setDataSource(Context var1, Uri var2) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
// LogUtils.d(TAG, "setDataSource 2:" + var2.getPath());
// LogUtils.d(TAG2, "设置播放地址:" + var2.getPath());
// DispatchQueue.main.async(() -> {
// LogUtils.d(TAG, "setDataSource Uri:" + var2);
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "ExoPlayer == null");
// return;
// }
// initSeekTime();
// if (null != var2) {
// String path = var2.toString();
// playUrl = path;
// mMediaItem = MediaItem.fromUri(var2);
// LogUtils.d("setDataSource () statue starting analysis playUrl:" + playUrl);
// }
// });
// }
//
//
// @Override
// public void setDataSource(Context var1, Uri var2, Map<String, String> var3) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
// LogUtils.d(TAG, "setDataSource 3:" + var2.toString());
// LogUtils.d(TAG2, "设置播放地址:" + var2.toString());
// DispatchQueue.main.async(() -> {
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return;
// }
// initSeekTime();
// if (null != var2) {
// String path = var2.toString();
// playUrl = path;
// mMediaItem = MediaItem.fromUri(var2);
// LogUtils.d("setDataSource () statue starting analysis play");
// }
// });
// }
//
// @Override
// public void setDataSource(FileDescriptor var1) throws IOException, IllegalArgumentException, IllegalStateException {
// //no support
// LogUtils.d(TAG, "setDataSource 4");
// }
//
// @Override
// public void setDataSource(String var1) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
// LogUtils.d(TAG, "setDataSource 5:" + var1);
// LogUtils.d(TAG2, "设置播放地址:" + var1);
// DispatchQueue.main.async(() -> {
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return;
// }
// initSeekTime();
// playUrl = var1;
// mMediaItem = MediaItem.fromUri(var1);
// LogUtils.d("statue setDataSource() starting analysis playUrl:" + playUrl);
// });
// }
//
// @Override
// public String getDataSource() {
// LogUtils.d(TAG, "getDataSource");
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return null;
// }
// return playUrl;
// }
//
// @Override
// public void prepareAsync() throws IllegalStateException {
// LogUtils.d(TAG, "prepareAsync");
// LogUtils.d(TAG2, "开始解码准备播放");
// DispatchQueue.main.async(() -> {
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return;
// }
// if (mInternalPlayer == null)
// return;
// if (mMediaItem == null) return;
// if (mSpeedPlaybackParameters != null) {
// mInternalPlayer.setPlaybackParameters(mSpeedPlaybackParameters);
// }
// mIsPreparing = true;
// mInternalPlayer.setMediaItem(mMediaItem);
// mInternalPlayer.setPlayWhenReady(false);
// mInternalPlayer.prepare();
// });
// }
//
// @Override
// public void start() throws IllegalStateException {
// LogUtils.d(TAG, "start");
// LogUtils.d(TAG2, "开始缓冲");
// DispatchQueue.main.async(() -> {
// LogUtils.d(TAG, "statue starting analysis start()");
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return;
// }
// mInternalPlayer.setPlayWhenReady(true);
// });
// }
//
// @Override
// public void stop() throws IllegalStateException {
// LogUtils.d(TAG, "stop");
// LogUtils.d(TAG2, "停止播放");
// DispatchQueue.main.async(() -> {
// LogUtils.d(TAG, "statue stop:");
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return;
// }
// mInternalPlayer.stop();
// });
// }
//
//
// @Override
// public void pause() throws IllegalStateException {
// LogUtils.d(TAG, "pause");
// LogUtils.d(TAG2, "暂停播放");
// DispatchQueue.main.async(() -> {
// LogUtils.d(TAG, "statue pause:");
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return;
// }
// mInternalPlayer.setPlayWhenReady(false);
//
// });
// }
//
// @Override
// public void setScreenOnWhilePlaying(boolean var1) {
// LogUtils.d(TAG, "setScreenOnWhilePlaying");
// //no support
// }
//
// @Override
// public int getVideoWidth() {
// LogUtils.d(TAG, "getVideoWidth");
// //no support
// //电视看点
// if (videoWidth == 0) {
// videoWidth = 1920;
// }
// LogUtils.d(TAG, "getVideoWidth videoWidth:" + videoWidth);
// return videoWidth;
// }
//
// @Override
// public int getVideoHeight() {
// LogUtils.d(TAG, "getVideoHeight");
// //no support
// if (videoHeight == 0) {
// videoHeight = 1080;
// }
// LogUtils.d(TAG, "getVideoHeight videoHeight:" + videoHeight);
// return videoHeight;
// }
//
// @Override
// public boolean isPlaying() {
// LogUtils.d(TAG, "isPlaying");
// if (isMainThread()) {
// if (mInternalPlayer == null) {
// return false;
// } else {
// int state = mInternalPlayer.getPlaybackState();
// if (state == Player.STATE_BUFFERING && isSeek) {
// LogUtils.d(TAG, "isPlaying seekTo: main thread isPlaying():=isSeek" + isSeek);
// return true;
// }
// isPlaying = mInternalPlayer.isPlaying();
// LogUtils.d(TAG, "isPlaying main thread isPlaying():==" + isPlaying);
// return mInternalPlayer.isPlaying();
// }
// } else {
// DispatchQueue.main.async(() -> {
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "isPlaying mInternalPlayer == null");
// isPlaying = false;
// } else {
// isPlaying = mInternalPlayer.isPlaying();
// LogUtils.d(TAG, "isPlaying isPlaying:33 :" + isPlaying);
// }
// });
// try {
// //20比较合适
// Thread.sleep(20);
// } catch (Exception ex) {
// ex.printStackTrace();
// }
// }
// LogUtils.d(TAG, "isPlaying 4:" + isPlaying);
// return isPlaying;
// }
//
// @Override
// public void seekTo(long time) throws IllegalStateException {
// LogUtils.d(TAG, "seekTo:" + time);
// LogUtils.d(TAG2, "seekTo播放:" + time);
// isSeek = true;
// DispatchQueue.main.async(() -> {
// LogUtils.d(TAG, "seekTo statue starting analysis seekTo:" + time);
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "iMediaPlayer == null");
// return;
// }
// if (time < 0) {
// LogUtils.d(TAG, "seekTo value =" + time);
// return;
// }
// mInternalPlayer.seekTo(time);
// });
//
// }
//
// @Override
// public long getCurrentPosition() {
// LogUtils.d(TAG, "getCurrentPosition");
// if (isMainThread()) {
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "iMediaPlayer == null");
// currentPosition = 0;
// return 0;
// }
// currentPosition = mInternalPlayer.getCurrentPosition();
// LogUtils.d(TAG, "getCurrentPosition:" + mInternalPlayer.getCurrentPosition());
// return currentPosition;
// }
// return currentPosition;
// }
//
// @Override
// public long getDuration() {
// LogUtils.d(TAG, "getDuration");
// if (isMainThread()) {
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// duration = 0;
// } else {
// duration = mInternalPlayer.getDuration();
// LogUtils.d(TAG, "main Thread getDuration:11 :" + duration);
// return duration;
// }
// }
// return duration;
// }
//
// public boolean isMainThread() {
// return Looper.myLooper() == Looper.getMainLooper();
// }
//
//
// @Override
// public void setVolume(float leftVolume, float rightVolume) {
// LogUtils.d(TAG, "setVolume:" + leftVolume + " " + rightVolume);
// DispatchQueue.main.async(() -> {
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return;
// }
// LogUtils.d(TAG, "setVolume leftVolume:" + leftVolume + "|| rightVolume:" + rightVolume);
// if (mInternalPlayer != null)
// mInternalPlayer.setVolume((leftVolume + rightVolume) / 2);
// });
// }
//
// @Override
// public int getAudioSessionId() {
// LogUtils.d(TAG, "getAudioSessionId");
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "iMediaPlayer == null");
// return 0;
// }
// return mInternalPlayer.getAudioSessionId();
// }
//
// @Override
// public MediaInfo getMediaInfo() {
// return null;
// }
//
// @Override
// public boolean isPlayable() {
// LogUtils.d(TAG, "isPlayable");
// //no support
// return true;
// }
//
// private boolean isFirst_BUFFERING_START = true;
// private boolean isVideoFrameMetadata = false;
//
// /***
// * ExoMediaPlayer播放相关回调
// * */
// @Override
// public void onPlaybackStateChanged(int playbackState) {
// LogUtils.d(TAG, "onPlaybackStateChanged");
// LogUtils.d(TAG, "onPlaybackStateChanged infoListener.onInfo mIsPreparing:" + mIsPreparing + " isSeek:" + isSeek);
// LogUtils.d(TAG, "onPlaybackStateChanged infoListener.onInfo:" + playbackState);
// if (mIsPreparing) {
// if (playbackState == Player.STATE_READY) {
// LogUtils.d(TAG, "onPlaybackStateChanged starting analysis exoPlayer statue: onPrepared");
// notifyOnPrepared();
// if (!isSeek) {
// notifyOnInfo(MEDIA_INFO_RENDERING_START, 0);
// }
// mIsPreparing = false;
// LogUtils.d(TAG2, "缓冲完毕开始展示播放画面1:" + isSeek);
// } else if (playbackState == Player.STATE_BUFFERING) {
// LogUtils.d(TAG2, "解码完毕准备缓冲");
// } else if (playbackState == Player.STATE_ENDED) {
// LogUtils.d(TAG2, "播放器缓冲结束");
// } else if (playbackState == Player.STATE_IDLE) {
// }
// } else {
// switch (playbackState) {
// case Player.STATE_BUFFERING:
// LogUtils.d(TAG2, "正在缓冲");
// LogUtils.d(TAG, "onPlaybackStateChanged starting analysis infoListener.onInfo:" + MEDIA_INFO_BUFFERING_START + " percentage:" + getBufferPercentage());
// notifyOnInfo(MEDIA_INFO_BUFFERING_START, 0);
//
// break;
// case Player.STATE_READY:
// //缓冲结束 缓冲结束,可以开始播放
// if (isFirst_BUFFERING_START) {
// notifyOnInfo(MEDIA_INFO_BUFFERING_START, 0);
// }
// notifyOnInfo(MEDIA_INFO_BUFFERING_START, 0);
// isVideoFrameMetadata = true;
// notifyOnInfo(MEDIA_INFO_RENDERING_START, 0);
// LogUtils.d(TAG2, "缓冲完毕开始展示播放画面2:"+ isSeek);
// LogUtils.d(TAG, "onPlaybackStateChanged starting analysis infoListener.onInfo: " + MEDIA_INFO_BUFFERING_END);
// break;
// case Player.STATE_ENDED:
// LogUtils.d(TAG2, "播放器缓冲结束");
// notifyOnCompletion();
// LogUtils.d(TAG2, "播放器准备完毕");
// LogUtils.d(TAG, "onPlaybackStateChanged completionListener.onCompletion:");
// initSeekTime();
// break;
// case Player.STATE_IDLE:
// break;
// }
// }
// }
//
// @Override
// public void onPlayerError(PlaybackException error) {
// LogUtils.d(TAG, "onPlayerError");
// String errorMessage = "code:" + error.errorCode + " ||message:" + error.getMessage();
// LogUtils.d(TAG2, "播放器报错 " + errorMessage);
// initSeekTime();
// notifyOnError(error.errorCode, errorMessage.contains("Format")?264:9999);
// }
//
// @Override
// public void onRenderedFirstFrame() {
// LogUtils.d(TAG, "onRenderedFirstFrame");
// }
//
// @Override
// public void onPositionDiscontinuity(Player.PositionInfo oldPosition, Player.PositionInfo newPosition, int reason) {
// LogUtils.d(TAG, "onPositionDiscontinuity");
// isSeek = false;
// DispatchQueue.main.async(() -> {
// //doSeekComplete(mInternalPlayer);
// // Seek 操作完成
//// if (null != seekCompleteListener) {
//// LogUtils.d(TAG, "onPositionDiscontinuity seekCompleteListener() oldPosition:" + oldPosition.positionMs + " newPosition:" + newPosition.positionMs);
//// //seekCompleteListener.onSeekComplete(mInternalPlayer);
//// }
// LogUtils.d(TAG, "onPositionDiscontinuity seekCompleteListener() oldPosition:" + oldPosition.positionMs + " newPosition:" + newPosition.positionMs);
// notifyOnSeekComplete();
// });
// }
//
// @Override
// public void onVideoSizeChanged(VideoSize videoSize) {
// LogUtils.d(TAG, "onVideoSizeChanged");
// videoWidth = videoSize.width;
// videoHeight = videoSize.height;
// LogUtils.d(TAG, "onVideoSizeChanged videoWidth:" + videoWidth + " videoHeight:" + videoHeight);
// notifyOnVideoSizeChanged(videoSize.width, videoSize.height, 0, 0);
// }
//
//
// // int MEDIA_INFO_UNKNOWN = 1;//未知信息
//// int MEDIA_INFO_STARTED_AS_NEXT = 2;//播放下一条
//// int MEDIA_INFO_VIDEO_RENDERING_START = 3;//视频开始整备中,准备渲染
//// int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700;//视频日志跟踪
//// int MEDIA_INFO_BUFFERING_START = 701;//开始缓冲中 开始缓冲
//// int MEDIA_INFO_BUFFERING_END = 702;//缓冲结束
//// int MEDIA_INFO_NETWORK_BANDWIDTH = 703;//网络带宽,网速方面
//// int MEDIA_INFO_BAD_INTERLEAVING = 800;//
//// int MEDIA_INFO_NOT_SEEKABLE = 801;//不可设置播放位置,直播方面
//// int MEDIA_INFO_METADATA_UPDATE = 802;//
//// int MEDIA_INFO_TIMED_TEXT_ERROR = 900;
//// int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901;//不支持字幕
//// int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902;//字幕超时
//// int MEDIA_INFO_VIDEO_INTERRUPT= -10000;//数据连接中断,一般是视频源有问题或者数据格式不支持,比如音频不是AAC之类的
//// int MEDIA_INFO_VIDEO_ROTATION_CHANGED = 10001;//视频方向改变,视频选择信息
//// int MEDIA_INFO_AUDIO_RENDERING_START = 10002;//音频开始整备中
//// int MEDIA_ERROR_SERVER_DIED = 100;//服务挂掉,视频中断,一般是视频源异常或者不支持的视频类型。
//// int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200;//数据错误没有有效的回收
//// int MEDIA_ERROR_IO = -1004;//IO 错误
//// int MEDIA_ERROR_MALFORMED = -1007;
//// int MEDIA_ERROR_UNSUPPORTED = -1010;//数据不支持
//// int MEDIA_ERROR_TIMED_OUT = -110;//数据超时
////
//// int MEDIA_INFO_VIDEO_RENDERING_START = 3;//视频准备渲染
//// int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200;//数据错误没有有效的回收
//// int MEDIA_INFO_BUFFERING_START = 701;//开始缓冲
//// int MEDIA_INFO_BUFFERING_END = 702;//缓冲结束
//// int MEDIA_INFO_VIDEO_ROTATION_CHANGED = 10001;//视频选择信息
//// int MEDIA_ERROR_SERVER_DIED = 100;//视频中断,一般是视频源异常或者不支持的视频类型。
//// int MEDIA_ERROR_IJK_PLAYER = -10000,//一般是视频源有问题或者数据格式不支持,比如音频不是AAC之类的
//// @Override
//// public void setOnErrorListener(OnErrorListener var1) {
//// LogUtils.d(TAG, "setOnErrorListener");
//// //错误信息不返回
//// DispatchQueue.main.async(() -> {
//// if (mInternalPlayer == null) {
//// LogUtils.d(TAG, "mInternalPlayer == null");
//// return;
//// }
//// errorListener = var1;
//// });
//// }
//
// /**
// * int MEDIA INFO VIDEO RENDERING START = 3;//视频准备渲染
// * int MEDIA INFO BUFFERING START = 701;//开始缓冲
// * int MEDIA INFO BUFFERING END = 702;//缓冲结束
// * int MEDIA INFO VIDEO ROTATION CHANGED = 10001;//视频选择信息
// * int MEDIA ERROR SERVER DIED = 100;//视频中断,一般是视频源异常或者不支持的视频类型。
// * int MEDIA ERROR IJK PLAYER = -10800,//一般是视频源有问题或者数据格式不支持,比如音频不是AAC之类的
// * int MEDIA ERROR NOT VALID FOR PROGRESSIVE PLAYBACK = 200;//数据错误没有有效的回收
// */
//// @Override
//// public void setOnInfoListener(OnInfoListener var1) {
//// LogUtils.d(TAG, "setOnInfoListener");
//// DispatchQueue.main.async(() -> {
//// if (mInternalPlayer == null) {
//// LogUtils.d(TAG, "mInternalPlayer == null");
//// return;
//// }
//// infoListener = var1;
//// });
//// }
// @Override
// public void setAudioStreamType(int var1) {
// LogUtils.d(TAG, "setAudioStreamType");
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return;
// }
// //mInternalPlayer.setAudioStreamType(var1);
// //no support
// }
//
// @Override
// public void setKeepInBackground(boolean var1) {
// LogUtils.d(TAG, "setKeepInBackground");
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// }
// //mInternalPlayer.set
// //no support
// }
//
// @Override
// public int getVideoSarNum() {
// LogUtils.d(TAG, "getVideoSarNum");
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return 0;
// }
// //no support
// return 0;
// }
//
// @Override
// public int getVideoSarDen() {
// LogUtils.d(TAG, "getVideoSarDen");
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return 0;
// }
// //no support
// return 0;
// }
//
// @Override
// public int getBufferPercentage() {
// LogUtils.d(TAG, "getBufferPercentage:" + mInternalPlayer.getBufferedPercentage());
// return mInternalPlayer == null ? 0 : mInternalPlayer.getBufferedPercentage();
// }
//
// @Override
// public void setWakeMode(Context var1, int var2) {
// LogUtils.d(TAG, "setWakeMode");
// DispatchQueue.main.async(() -> {
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return;
// }
// mInternalPlayer.setWakeMode(var2);
// });
// }
//
// @Override
// public void setLooping(boolean isLooping) {
// LogUtils.d(TAG, "setLooping:" + isLooping);
// DispatchQueue.main.async(() -> {
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return;
// }
// if (mInternalPlayer != null)
// mInternalPlayer.setRepeatMode(isLooping ? Player.REPEAT_MODE_ALL : Player.REPEAT_MODE_OFF);
// });
// }
//
// @Override
// public boolean isLooping() {
// LogUtils.d(TAG, "isLooping");
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "iMediaPlayer == null");
// return false;
// }
//
// if (mInternalPlayer.getRepeatMode() == Player.REPEAT_MODE_ALL) {
// return true;
// }
// return false;
// }
//
// @Override
// public ITrackInfo[] getTrackInfo() {
// return new ITrackInfo[0];
// }
//
// private final Handler mainHandler = new Handler(Looper.getMainLooper());
//
// @Override
// public void release() {
// LogUtils.d(TAG, "release:");
// LogUtils.d(TAG2, "释放播放器资源");
// mainHandler.post(new Runnable() {
// @Override
// public void run() {
// LogUtils.d(TAG, "statue release");
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return;
// }
// initSeekTime();
// if (mInternalPlayer != null) {
// mInternalPlayer.removeListener(PkHighExoMediaPlayer.this);
// mInternalPlayer.release();
// mInternalPlayer = null;
// }
// mIsPreparing = false;
// mSpeedPlaybackParameters = null;
// }
// });
//// DispatchQueue.main.async(() -> {
//// LogUtils.d(TAG, "statue release");
//// if (mInternalPlayer == null) {
//// LogUtils.d(TAG, "mInternalPlayer == null");
//// return;
//// }
//// addPlayRecord();
//// initSeekTime();
//// if (mInternalPlayer != null) {
//// mInternalPlayer.removeListener(this);
//// mInternalPlayer.release();
//// mInternalPlayer = null;
//// }
//// mIsPreparing = false;
//// mSpeedPlaybackParameters = null;
//// });
// }
//
// @Override
// public void reset() {
// LogUtils.d(TAG, "reset");
// LogUtils.d(TAG2, "重置播放器资源:");
// DispatchQueue.main.async(() -> {
// LogUtils.d(TAG, "reset start");
// if (mInternalPlayer == null) {
// LogUtils.d(TAG, "mInternalPlayer == null");
// return;
// }
// initSeekTime();
// if (mInternalPlayer != null) {
// mInternalPlayer.stop();
// mInternalPlayer.clearMediaItems();
// mIsPreparing = false;
// playUrl = "";
// }
// LogUtils.d(TAG, "reset end");
// });
// }
//
// private void initMediaListener() {
// LogUtils.d(TAG, "initMediaListener");
// mInternalPlayer.addListener(this);
// }
//
// /**
// * 恢复默认值
// */
// private void initSeekTime() {
// LogUtils.d(TAG, "initSeekTime");
// DispatchQueue.main.async(() -> {
// isSeek = false;
// duration = 0;
// currentPosition = 0;
// videoWidth = 0;
// videoHeight = 0;
// });
// }
//
// @Override
// public void onTimelineChanged(Timeline timeline, int reason) {
// LogUtils.d(TAG, "onTimelineChanged");
// Player.Listener.super.onTimelineChanged(timeline, reason);
// }
//
//
//}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true">
<shape android:shape="rectangle">
<gradient android:type="linear" android:useLevel="true" android:startColor="#ff8e26" android:endColor="#ff7618" android:angle="90" />
<corners android:radius="@dimen/qb_px_6" />
</shape>
</item>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<gradient android:type="linear" android:useLevel="true" android:startColor="#ff8e26" android:endColor="#ff7618" android:angle="90" />
<corners android:radius="@dimen/qb_px_6" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="#34FFFFFF"/>
<corners android:radius="@dimen/qb_px_6" />
</shape>
</item>
</selector>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient android:type="linear"
android:startColor="#00000000"
android:endColor="#ff000000"
android:angle="270" />
</shape>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient android:type="linear"
android:startColor="#ff000000"
android:centerColor="#b0000000"
android:endColor="#000f1923"
android:angle="270" />
</shape>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true">
<shape android:shape="oval">
<solid
android:color="#ff8e26"
/>
</shape>
</item>
</selector>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="2dp"
android:color="#FFFFFF" />
<solid android:color="#00000000" />
</shape>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="2dp"
android:color="#FFFFFF" />
<solid android:color="#4DFFFFFF" />
</shape>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/bg_white_round_border_pressed" android:state_pressed="true" />
<item android:drawable="@drawable/bg_white_round_border_pressed" android:state_focused="true" />
<item android:drawable="@drawable/bg_white_round_border_normal" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item
android:duration="43"
android:drawable="@drawable/loading000"/>
<item
android:duration="43"
android:drawable="@drawable/loading00"/>
<item
android:duration="43"
android:drawable="@drawable/loading01"/>
<item
android:duration="43"
android:drawable="@drawable/loading02"/>
<item
android:duration="43"
android:drawable="@drawable/loading03"/>
<item
android:duration="43"
android:drawable="@drawable/loading04"/>
<item
android:duration="43"
android:drawable="@drawable/loading05"/>
<item
android:duration="43"
android:drawable="@drawable/loading06"/>
<item
android:duration="43"
android:drawable="@drawable/loading07"/>
<item
android:duration="43"
android:drawable="@drawable/loading08"/>
<item
android:duration="43"
android:drawable="@drawable/loading09"/>
<item
android:duration="43"
android:drawable="@drawable/loading10"/>
<item
android:duration="43"
android:drawable="@drawable/loading11"/>
<item
android:duration="43"
android:drawable="@drawable/loading12"/>
<item
android:duration="43"
android:drawable="@drawable/loading13"/>
<item
android:duration="43"
android:drawable="@drawable/loading14"/>
<item
android:duration="43"
android:drawable="@drawable/loading15"/>
<item
android:duration="43"
android:drawable="@drawable/loading16"/>
<item
android:duration="43"
android:drawable="@drawable/loading17"/>
<item
android:duration="43"
android:drawable="@drawable/loading18"/>
<item
android:duration="43"
android:drawable="@drawable/loading19"/>
<item
android:duration="43"
android:drawable="@drawable/loading20"/>
<item
android:duration="43"
android:drawable="@drawable/loading21"/>
<item
android:duration="43"
android:drawable="@drawable/loading22"/>
</animation-list>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<solid android:color="@color/white8" />
<corners android:radius="@dimen/qb_px_2" />
</shape>
</item>
<!-- 第二进度图 -->
<item android:id="@android:id/secondaryProgress">
<shape>
<solid android:color="@color/white_transparent_60"/>
<corners android:radius="@dimen/qb_px_2" />
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<solid android:color="#FF7417" />
<corners android:radius="@dimen/qb_px_2" />
</shape>
</clip>
</item>
</layer-list>

View File

@@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<size android:width="@dimen/qb_px_20" android:height="@dimen/qb_px_20"/>
<solid android:color="#FFFFFFFF"/>
<stroke android:width="@dimen/qb_px_2" android:color="#66000000"/>
</shape>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true">
<shape android:shape="rectangle">
<solid android:color="#1AFFFFFF" />
<corners android:radius="4dp" />
<stroke
android:width="2dp"
android:color="#FF6B00" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<corners android:radius="4dp" />
</shape>
</item>
</selector>

View File

@@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="68"
android:viewportHeight="68">
<path
android:pathData="M34,34m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"/>
<path
android:pathData="M28,24L30,24A2,2 0,0 1,32 26L32,42A2,2 0,0 1,30 44L28,44A2,2 0,0 1,26 42L26,26A2,2 0,0 1,28 24z"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
<path
android:pathData="M38,24L40,24A2,2 0,0 1,42 26L42,42A2,2 0,0 1,40 44L38,44A2,2 0,0 1,36 42L36,26A2,2 0,0 1,38 24z"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

View File

@@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="68"
android:viewportHeight="68">
<path
android:pathData="M34,34m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"/>
<path
android:pathData="M28,24L28,44L44,34L28,24Z"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -0,0 +1,19 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<size android:height="@dimen/qb_px_12"/>
<corners android:radius="@dimen/qb_px_12"/>
<solid android:color="#50000000"/>
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape android:shape="rectangle">
<size android:height="@dimen/qb_px_12"/>
<corners android:radius="@dimen/qb_px_12"/>
<gradient android:startColor="@android:color/holo_orange_light"
android:endColor="@android:color/holo_orange_dark"/>
</shape>
</clip>
</item>
</layer-list>

View File

@@ -0,0 +1,321 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:background="#00000000"
android:layoutDirection="ltr"
android:orientation="vertical"
android:visibility="visible">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="@dimen/qb_px_240"
android:background="@drawable/bg_control_top"
android:focusable="false">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginTop="@dimen/qb_px_28"
android:layout_marginRight="@dimen/qb_px_40"
android:gravity="center_vertical">
<TextView
android:id="@+id/play_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/qb_px_40"
android:ellipsize="end"
android:maxEms="20"
android:singleLine="true"
android:text="恐龙当家"
android:textColor="@color/white"
android:textSize="@dimen/mg_sp_21"
android:textStyle="bold"
android:visibility="visible" />
<TextClock
android:id="@+id/id_tc_clock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:focusable="false"
android:format12Hour="HH:mm"
android:format24Hour="HH:mm"
android:textColor="#FFFFFF"
android:textSize="@dimen/mg_sp_22"
android:textStyle="normal"
android:visibility="gone" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:gravity="right"
android:orientation="horizontal">
<ImageView
android:id="@+id/id_tip_img1"
android:layout_width="@dimen/qb_px_69"
android:layout_height="@dimen/qb_px_27"
android:focusable="false"
android:src="@drawable/videotip2"
android:visibility="visible" />
<ImageView
android:id="@+id/id_tip_img2"
android:layout_width="@dimen/qb_px_233"
android:layout_height="@dimen/qb_px_27"
android:layout_marginLeft="@dimen/qb_px_12"
android:focusable="false"
android:src="@drawable/videotip1"
android:visibility="visible" />
</LinearLayout>
</RelativeLayout>
</RelativeLayout>
<RelativeLayout
android:id="@+id/play_contro_rl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@drawable/bg_control_bottom"
android:focusable="false"
android:paddingLeft="@dimen/qb_px_40"
android:paddingTop="@dimen/qb_px_24"
android:paddingRight="@dimen/qb_px_40"
android:paddingBottom="@dimen/qb_px_20">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_settings_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/jujitv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_btn_video_selector"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="false"
android:gravity="center"
android:hint="三个字"
android:nextFocusDown="@id/mediacontroller_progress"
android:nextFocusLeft="@id/jujitv"
android:nextFocusRight="@id/id_tv_resolution"
android:nextFocusUp="@id/jujitv"
android:paddingHorizontal="@dimen/qb_px_15"
android:paddingVertical="@dimen/qb_px_7"
android:text="选集"
android:textColor="@color/white"
android:textSize="@dimen/mg_sp_21" />
<TextView
android:id="@+id/id_tv_resolution"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/qb_px_8"
android:background="@drawable/bg_btn_video_selector"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="false"
android:gravity="center"
android:hint="三个字"
android:nextFocusDown="@id/mediacontroller_progress"
android:nextFocusLeft="@id/jujitv"
android:nextFocusRight="@id/id_tv_speed"
android:nextFocusUp="@id/id_tv_resolution"
android:paddingHorizontal="@dimen/qb_px_15"
android:paddingVertical="@dimen/qb_px_7"
android:text="清晰度"
android:textColor="@color/white"
android:textSize="@dimen/mg_sp_21" />
<TextView
android:id="@+id/id_tv_speed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/qb_px_8"
android:background="@drawable/bg_btn_video_selector"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="false"
android:gravity="center"
android:hint="三个字"
android:nextFocusDown="@id/mediacontroller_progress"
android:nextFocusLeft="@id/id_tv_resolution"
android:nextFocusRight="@id/pause"
android:nextFocusUp="@id/id_tv_speed"
android:paddingHorizontal="@dimen/qb_px_15"
android:paddingVertical="@dimen/qb_px_7"
android:text="倍速"
android:textColor="@color/white"
android:textSize="@dimen/mg_sp_21" />
</LinearLayout>
<Space
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/pause"
android:layout_width="@dimen/qb_px_52"
android:layout_height="@dimen/qb_px_52"
android:background="@drawable/bg_pause_video_selector"
android:focusable="true"
android:nextFocusDown="@id/mediacontroller_progress"
android:nextFocusLeft="@id/id_tv_speed"
android:nextFocusRight="@id/id_back_btn"
android:nextFocusUp="@id/pause"
android:scaleType="fitXY"
android:src="@drawable/svg_media_play_icon" />
<ImageView
android:id="@+id/id_back_btn"
android:layout_width="@dimen/qb_px_50"
android:layout_height="@dimen/qb_px_50"
android:layout_marginStart="@dimen/qb_px_12"
android:clickable="true"
android:focusable="true"
android:background="@drawable/bg_pause_video_selector"
android:nextFocusDown="@id/mediacontroller_progress"
android:nextFocusLeft="@id/pause"
android:nextFocusRight="@id/id_back_btn"
android:nextFocusUp="@id/id_back_btn"
android:padding="@dimen/qb_px_10"
android:visibility="visible" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/ll_progress_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/qb_px_20"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/time_current"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="@dimen/mg_sp_20" />
<SeekBar
android:id="@+id/mediacontroller_progress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/qb_px_20"
android:layout_marginEnd="@dimen/qb_px_20"
android:layout_weight="1"
android:focusable="true"
android:focusableInTouchMode="true"
android:max="1000"
android:maxHeight="4dp"
android:minHeight="4dp"
android:nextFocusDown="@id/mediacontroller_progress"
android:nextFocusLeft="@id/mediacontroller_progress"
android:nextFocusRight="@id/mediacontroller_progress"
android:nextFocusUp="@id/pause"
android:paddingLeft="@dimen/qb_px_8"
android:paddingTop="@dimen/qb_px_6"
android:paddingRight="@dimen/qb_px_8"
android:paddingBottom="@dimen/qb_px_6"
android:progressDrawable="@drawable/ondemand_seekbar_style"
android:thumb="@mipmap/dot" />
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="@dimen/mg_sp_20" />
</LinearLayout>
</LinearLayout>
</RelativeLayout>
<ImageButton
android:id="@+id/centerPause"
android:visibility="gone"
android:layout_width="@dimen/qb_px_160"
android:layout_height="@dimen/qb_px_160"
android:layout_centerInParent="true"
android:layout_marginBottom="@dimen/qb_px_26"
android:background="@drawable/ic_media_pause"/>
<RelativeLayout
android:id="@+id/touch_volume_panel"
android:layout_width="@dimen/qb_px_72"
android:layout_height="@dimen/qb_px_320"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="@dimen/qb_px_160"
android:gravity="center_horizontal"
tools:visibility="visible"
android:visibility="gone">
<TextView
android:id="@+id/tv_touch_volume_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="0"
android:textColor="@color/white"
android:textSize="@dimen/mg_sp_20"
android:textStyle="bold" />
<FrameLayout
android:layout_width="@dimen/qb_px_40"
android:layout_height="@dimen/qb_px_240"
android:layout_below="@id/tv_touch_volume_value"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/qb_px_20">
<SeekBar
android:id="@+id/sb_touch_volume"
android:layout_width="@dimen/qb_px_240"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:max="100"
android:maxHeight="@dimen/qb_px_12"
android:minHeight="@dimen/qb_px_12"
android:rotation="270"
android:progressDrawable="@drawable/volume_seek_progress"
android:thumb="@mipmap/dot" />
</FrameLayout>
</RelativeLayout>
</RelativeLayout>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:gravity="bottom"
android:layout_height="@dimen/qb_px_420">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/id_rv_type_page"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/id_rv_type"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/id_rv_function"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--TEXT-->
<!--ICON-->
<!--透明度20-->
<!--透明度30-->
<!--透明度40-->
<!--透明度50-->
<!--透明度60-->
<!--透明度70-->
<!--透明度80-->
<!--透明度20-->
<!--透明色 -->
<!--透明色 -->
<color name="white_transparent_60">#99FFFFFF</color><!--透明色 -->
<color name="white">#FFFFFF</color>
<!--透明度0.2白色-->
<color name="white8">#CCFFFFFF</color><!--透明度0.8白色-->
</resources>

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--PAGE TITLE-->
<!--Light-->
<!--Light-->
<!--Bold-->
<!--Bold-->
<!--Bold-->
<!--Bold-->
<!--BODY-->
<!--Bold-->
<!--Bold-->
<!--Bold-->
<!--Light-->
<!--Light-->
<!--Bold-->
<!--Light-->
<!--Light-->
<!--Light-->
<!--Segoe WP Regular-->
<!--Light-->
<!--Light-->
<!--Light-->
<!--Bold-->
<!--Bold-->
<!--Light-->
<!--Light-->
<!--Light-->
<!--Bold-->
<!--Bold-->
<!--Bold-->
<!--Bold-->
<!--Bold-->
<!--Bold-->
<!--Bold-->
<!--Bold-->
<!--Light-->
<!--Light-->
<!--Light-->
<!--Light-->
<!--Light-->
</resources>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@@ -0,0 +1 @@
<resources></resources>

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@@ -0,0 +1,17 @@
package com.pukka.mediaplayertv;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}