话不多说,先上图(眼睛模式)
参考的开源库——传送门 MD360Player4Android
在开源库的基础上,做了菜单和播放器逻辑。
VR功能的部分通过依赖vrlib来实现,视频播放的部分集成ijkPlayer
这里对MD360Player4Android中的一些代码做解释。
初始化VR播放器
@Override
protected MDVRLibrary createVRLibrary() {
return MDVRLibrary.with(this)
.displayMode(MDVRLibrary.DISPLAY_MODE_NORMAL) //默认360度全景
.interactiveMode(MDVRLibrary.INTERACTIVE_MODE_MOTION_WITH_TOUCH)//触摸和重力
.asVideo(new MDVRLibrary.IOnSurfaceReadyCallback() {
@Override
public void onSurfaceReady(Surface surface) {
mMediaPlayerWrapper.setSurface(surface);
}
})
.ifNotSupport(new MDVRLibrary.INotSupportCallback() {
@Override
public void onNotSupport(int mode) {
String tip = mode == MDVRLibrary.INTERACTIVE_MODE_MOTION
? "onNotSupport:MOTION" : "onNotSupport:" + String.valueOf(mode);
Toast.makeText(VrPlayerActivity.this, tip, Toast.LENGTH_SHORT).show();
}
})
.pinchConfig(new MDPinchConfig().setMin(1.0f).setMax(8.0f).setDefaultValue(0.1f))
.pinchEnabled(true)
.directorFactory(new MD360DirectorFactory() {
@Override
public MD360Director createDirector(int index) {
return MD360Director.builder().setPitch(90).build();
}
})
.projectionFactory(new CustomProjectionFactory()).listenGesture(new MDVRLibrary.IGestureListener() {
@Override
public void onClick(MotionEvent e) {
touchRl.setVisibility(View.VISIBLE);
if(mMediaPlayerWrapper.getPlayer().isPlaying()){
img_start_vr.setVisibility(View.GONE);
img_stop_vr.setVisibility(View.VISIBLE);
}else {
img_start_vr.setVisibility(View.VISIBLE);
img_stop_vr.setVisibility(View.GONE);
}
mHandler.sendEmptyMessage(MESSAGE_SHOW_PROGRESS);
hasMenuAction();
verticalSeekBar.setProgress(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) * 100 / mMaxVolume);
}
})
.barrelDistortionConfig(new BarrelDistortionConfig().setDefaultEnabled(false).setScale(0.95f))
.build(findViewById(R.id.gl_view));
}
主要的设置有displayMode——眼睛模式和全景模式,interactiveMode——这个字面上理解是交互模式,可以设置重力感应,触摸,或者禁止重力感应,禁止触摸等。
其实在M360PlayerActivity中,各种设置选项已经陈列出来了
public abstract class M360PlayerActivity extends AppCompatActivity {
private static final SparseArray<String> sDisplayMode = new SparseArray<>();
private static final SparseArray<String> sInteractiveMode = new SparseArray<>();
private static final SparseArray<String> sProjectionMode = new SparseArray<>();
private static final SparseArray<String> sAntiDistortion = new SparseArray<>();
private static final SparseArray<String> sPitchFilter = new SparseArray<>();
private static final SparseArray<String> sFlingEnabled = new SparseArray<>();
static {
sDisplayMode.put(MDVRLibrary.DISPLAY_MODE_NORMAL,"NORMAL");
sDisplayMode.put(MDVRLibrary.DISPLAY_MODE_GLASS,"GLASS");
sInteractiveMode.put(MDVRLibrary.INTERACTIVE_MODE_MOTION,"MOTION");
sInteractiveMode.put(MDVRLibrary.INTERACTIVE_MODE_TOUCH,"TOUCH");
sInteractiveMode.put(MDVRLibrary.INTERACTIVE_MODE_MOTION_WITH_TOUCH,"M & T");
sInteractiveMode.put(MDVRLibrary.INTERACTIVE_MODE_CARDBORAD_MOTION,"CARDBOARD M");
sInteractiveMode.put(MDVRLibrary.INTERACTIVE_MODE_CARDBORAD_MOTION_WITH_TOUCH,"CARDBOARD M&T");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_SPHERE,"SPHERE");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_DOME180,"DOME 180");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_DOME230,"DOME 230");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_DOME180_UPPER,"DOME 180 UPPER");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_DOME230_UPPER,"DOME 230 UPPER");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_STEREO_SPHERE_HORIZONTAL,"STEREO H SPHERE");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_STEREO_SPHERE_VERTICAL,"STEREO V SPHERE");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_PLANE_FIT,"PLANE FIT");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_PLANE_CROP,"PLANE CROP");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_PLANE_FULL,"PLANE FULL");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_MULTI_FISH_EYE_HORIZONTAL,"MULTI FISH EYE HORIZONTAL");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_MULTI_FISH_EYE_VERTICAL,"MULTI FISH EYE VERTICAL");
sAntiDistortion.put(1,"ANTI-ENABLE");
sAntiDistortion.put(0,"ANTI-DISABLE");
sPitchFilter.put(1,"FILTER PITCH");
sPitchFilter.put(0,"FILTER NOP");
sFlingEnabled.put(1, "FLING ENABLED");
sFlingEnabled.put(0, "FLING DISABLED");
}
}
在onSurfaceReady(Surface surface)的回调中,调用了 mMediaPlayerWrapper.setSurface(surface); 这里的MediaPlayerWrapper是一个封住了ijkPlayer的播放器。这里可以理解成在VR功能接入完毕后,又把视频的处理交还给了ijkPlayer。而ijkPlayer的使用,跟VR功能就没关系了。(理解有点生硬,完全是看单词翻译的有木有- -!)
项目实现大概就是这样,形容确切一点,就是一个具有VR功能的ijjPlayer
页面布局activity_md_using_surface_view.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<FrameLayout
android:id="@+id/framelayout_gl"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.opengl.GLSurfaceView
android:id="@+id/gl_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<!--视频菜单-->
<include layout="@layout/include_menu"/>
<ProgressBar
android:layout_centerInParent="true"
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
菜单布局include_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rl_touch_menu"
android:background="#33000000"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="44dp"
android:orientation="horizontal"
android:gravity="center_vertical"
>
<ImageView
android:id="@+id/img_back"
android:layout_marginLeft="15dp"
android:src="@drawable/back_normal"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_marginLeft="26dp"
android:gravity="center_vertical"
android:id="@+id/tv_vr_title"
android:textSize="16sp"
android:text="视频名字哦"
android:textColor="#ffffff"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
</LinearLayout>
<LinearLayout
android:layout_marginTop="13dp"
android:layout_alignParentRight="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginRight="18dp">
<Button
android:id="@+id/btn_vr_nor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="360°全景" />
<Button
android:id="@+id/btn_vr_glass"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="眼睛模式" />
</LinearLayout>
<LinearLayout
android:layout_centerVertical="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal">
<RelativeLayout
android:id="@+id/rl_voice_add"
android:padding="7dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:textColor="@color/white"
android:text="音量+"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
<com.vrplayerdemo.view.VerticalSeekBar
android:layout_marginTop="15dp"
android:id="@+id/seekbar_voice"
android:layout_width="100dp"
android:layout_height="160dp" />
<RelativeLayout
android:layout_marginTop="15dp"
android:id="@+id/rl_voice_sub"
android:padding="7dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:textColor="@color/white"
android:text="音量-"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
</LinearLayout>
<ImageView
android:clickable="true"
android:layout_marginRight="45dp"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:id="@+id/img_next_video"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/vr_next_nor"/>
<ImageView
android:id="@+id/img_stop_vr"
android:src="@drawable/btn_vr_stop"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/img_start_vr"
android:src="@drawable/btn_vr_start_nor"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"/>
<LinearLayout
android:layout_marginLeft="28dp"
android:layout_marginRight="28dp"
android:layout_marginBottom="17dp"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_current_play_time"
android:text="00:00"
android:textColor="#FFFFFF"
android:textSize="14sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<SeekBar
style="@style/SeekBarAppTheme"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:id="@+id/seekbar_play_video"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
/>
<TextView
android:id="@+id/tv_end_play_time"
android:text="12:05"
android:textColor="#FFFFFF"
android:textSize="14sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</RelativeLayout>
M360PlayerActivity代码
package com.vrplayerdemo.view;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.SparseArray;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import com.asha.vrlib.MDDirectorCamUpdate;
import com.asha.vrlib.MDVRLibrary;
import com.asha.vrlib.plugins.hotspot.IMDHotspot;
import com.asha.vrlib.texture.MD360BitmapTexture;
import com.vrplayerdemo.R;
import com.vrplayerdemo.VrPlayerActivity;
import com.vrplayerdemo.model.VideoModel;
import java.io.FileNotFoundException;
/**
* using MD360Renderer
*
* Created by hzqiujiadi on 16/1/22.
* hzqiujiadi ashqalcn@gmail.com
*/
public abstract class M360PlayerActivity extends AppCompatActivity {
private static final SparseArray<String> sDisplayMode = new SparseArray<>();
private static final SparseArray<String> sInteractiveMode = new SparseArray<>();
private static final SparseArray<String> sProjectionMode = new SparseArray<>();
private static final SparseArray<String> sAntiDistortion = new SparseArray<>();
private static final SparseArray<String> sPitchFilter = new SparseArray<>();
private static final SparseArray<String> sFlingEnabled = new SparseArray<>();
static {
sDisplayMode.put(MDVRLibrary.DISPLAY_MODE_NORMAL,"NORMAL");
sDisplayMode.put(MDVRLibrary.DISPLAY_MODE_GLASS,"GLASS");
sInteractiveMode.put(MDVRLibrary.INTERACTIVE_MODE_MOTION,"MOTION");
sInteractiveMode.put(MDVRLibrary.INTERACTIVE_MODE_TOUCH,"TOUCH");
sInteractiveMode.put(MDVRLibrary.INTERACTIVE_MODE_MOTION_WITH_TOUCH,"M & T");
sInteractiveMode.put(MDVRLibrary.INTERACTIVE_MODE_CARDBORAD_MOTION,"CARDBOARD M");
sInteractiveMode.put(MDVRLibrary.INTERACTIVE_MODE_CARDBORAD_MOTION_WITH_TOUCH,"CARDBOARD M&T");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_SPHERE,"SPHERE");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_DOME180,"DOME 180");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_DOME230,"DOME 230");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_DOME180_UPPER,"DOME 180 UPPER");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_DOME230_UPPER,"DOME 230 UPPER");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_STEREO_SPHERE_HORIZONTAL,"STEREO H SPHERE");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_STEREO_SPHERE_VERTICAL,"STEREO V SPHERE");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_PLANE_FIT,"PLANE FIT");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_PLANE_CROP,"PLANE CROP");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_PLANE_FULL,"PLANE FULL");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_MULTI_FISH_EYE_HORIZONTAL,"MULTI FISH EYE HORIZONTAL");
sProjectionMode.put(MDVRLibrary.PROJECTION_MODE_MULTI_FISH_EYE_VERTICAL,"MULTI FISH EYE VERTICAL");
sAntiDistortion.put(1,"ANTI-ENABLE");
sAntiDistortion.put(0,"ANTI-DISABLE");
sPitchFilter.put(1,"FILTER PITCH");
sPitchFilter.put(0,"FILTER NOP");
sFlingEnabled.put(1, "FLING ENABLED");
sFlingEnabled.put(0, "FLING DISABLED");
}
// public static void startVideo(Context context, Uri uri){
// start(context, uri,VideoPlayerActivity.class);
// }
// uri在userAlbumModel里,不是本地数据,不需要Uri格式
public static void startVideo(Context context, VideoModel videoModel, int playType){
start(context,videoModel,playType, VrPlayerActivity.class);
}
// public static void startBitmap(Context context, Uri uri){
// start(context, uri, BitmapPlayerActivity.class);
// }
private static void start(Context context,VideoModel videoModel,int playType,Class<? extends Activity> clz){
Intent i = new Intent(context,clz);
// i.setData(uri);
i.putExtra("videoModel",videoModel);
i.putExtra("playType",playType);
context.startActivity(i);
}
private MDVRLibrary mVRLibrary;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// no title
requestWindowFeature(Window.FEATURE_NO_TITLE);
// full screen
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
// set content view
setContentView(R.layout.activity_md_using_surface_view);
// init VR Library
mVRLibrary = createVRLibrary();
final Activity activity = this;
getVRLibrary().setEyePickChangedListener(new MDVRLibrary.IEyePickListener() {
@Override
public void onHotspotHit(IMDHotspot hotspot, long hitTimestamp) {
if (System.currentTimeMillis() - hitTimestamp > 5000){
getVRLibrary().resetEyePick();
}
}
});
}
private ValueAnimator animator;
private void startCameraAnimation(final MDDirectorCamUpdate cameraUpdate, PropertyValuesHolder... values){
if (animator != null){
animator.cancel();
}
animator = ValueAnimator.ofPropertyValuesHolder(values).setDuration(2000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float near = (float) animation.getAnimatedValue("near");
float eyeZ = (float) animation.getAnimatedValue("eyeZ");
float pitch = (float) animation.getAnimatedValue("pitch");
float yaw = (float) animation.getAnimatedValue("yaw");
float roll = (float) animation.getAnimatedValue("roll");
cameraUpdate.setEyeZ(eyeZ).setNearScale(near).setPitch(pitch).setYaw(yaw).setRoll(roll);
}
});
animator.start();
}
abstract protected MDVRLibrary createVRLibrary();
public MDVRLibrary getVRLibrary() {
return mVRLibrary;
}
@Override
protected void onResume() {
super.onResume();
mVRLibrary.onResume(this);
}
@Override
protected void onPause() {
super.onPause();
mVRLibrary.onPause(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
mVRLibrary.onDestroy();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mVRLibrary.onOrientationChanged(this);
}
protected Uri getUri() {
Intent i = getIntent();
if (i == null || i.getData() == null){
return null;
}
return i.getData();
}
public void cancelBusy(){
findViewById(R.id.progress).setVisibility(View.GONE);
}
public void busy(){
findViewById(R.id.progress).setVisibility(View.VISIBLE);
}
// android impl
private class AndroidProvider implements MDVRLibrary.IImageLoadProvider {
Activity activity;
public AndroidProvider(Activity activity) {
this.activity = activity;
}
@Override
public void onProvideBitmap(Uri uri, MD360BitmapTexture.Callback callback) {
try {
Bitmap bitmap = BitmapFactory.decodeStream(activity.getContentResolver().openInputStream(uri));
callback.texture(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
}
最后就是我们页面的主要逻辑,也是区别于MD360Player4Android,自己做的页面逻辑,让demo更像实际开发的项目。这里页面风格主要仿照尤果圈。
package com.vrplayerdemo;
import android.content.Context;
import android.graphics.Color;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import com.asha.vrlib.MD360Director;
import com.asha.vrlib.MD360DirectorFactory;
import com.asha.vrlib.MDVRLibrary;
import com.asha.vrlib.model.BarrelDistortionConfig;
import com.asha.vrlib.model.MDPinchConfig;
import com.vrplayerdemo.model.VideoModel;
import com.vrplayerdemo.view.CustomProjectionFactory;
import com.vrplayerdemo.view.M360PlayerActivity;
import com.vrplayerdemo.view.MediaPlayerWrapper;
import com.vrplayerdemo.view.VerticalSeekBar;
import tv.danmaku.ijk.media.player.IMediaPlayer;
public class VrPlayerActivity extends M360PlayerActivity implements View.OnClickListener{
/**
* 是否在拖动进度条中,默认为停止拖动,true为在拖动中,false为停止拖动
*/
private boolean isDragging;
/**
* 播放缓冲监听
*/
private int mCurrentBufferPercentage=0;
/**
* 同步进度
*/
private static final int MESSAGE_SHOW_PROGRESS = 1;
/**
* 5秒菜单操作,隐藏菜单面板
*/
private static final int MESSAGE_TOUCH_MENU = 3;
/**
* 开启倒计时
*/
private static final int MESSAGE_START_COUNTDOWN = 4;
/**
* 重新播放
*/
private static final int MESSAGE_PLAY_NEXT = 5;
private MediaPlayerWrapper mMediaPlayerWrapper = new MediaPlayerWrapper();
//显示菜单的全屏区域
private RelativeLayout touchRl;
//音量的seekbar
private VerticalSeekBar verticalSeekBar;
private RelativeLayout addVoice;
private RelativeLayout subVoice;
private Button btn_vr_nor;//全景模式
private Button btn_vr_glass; //眼镜模式
private ImageView img_stop_vr; //停止vr按钮
private ImageView img_start_vr;//继续vr按钮
private TextView endTimeTv; //总时长
private TextView currentTimeTv; //当前时长
private SeekBar playSeekbar; //视频播放进度
private TextView titleTv; //视频标题
private ImageView img_back; //返回键
private ImageView img_next_video; //下一部视频
//当前声音大小
private int volume;
//设备最大音量
private int mMaxVolume;
//音频管理器
private AudioManager audioManager;
/**
* 播放总时长
*/
private VideoModel videoModel;
private int playType=MDVRLibrary.DISPLAY_MODE_NORMAL;
private boolean isFirstGlass;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
videoModel= (VideoModel) getIntent().getSerializableExtra("videoModel");
playType =getIntent().getIntExtra("playType",MDVRLibrary.DISPLAY_MODE_NORMAL);
initView();
//初始化页面动画
touchRl.setVisibility(View.VISIBLE);
initMediaPlayer();
mHandler.sendEmptyMessage(MESSAGE_SHOW_PROGRESS);
}
//初始化播放器
private void initMediaPlayer() {
mMediaPlayerWrapper.init();
mMediaPlayerWrapper.setPreparedListener(new IMediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(IMediaPlayer mp) {
cancelBusy();
if (getVRLibrary() != null){
getVRLibrary().notifyPlayerChanged();
}
if(isFirstGlass){
isFirstGlass=false;
mMediaPlayerWrapper.pause();
}
}
});
mMediaPlayerWrapper.getPlayer().setOnErrorListener(new IMediaPlayer.OnErrorListener() {
@Override
public boolean onError(IMediaPlayer mp, int what, int extra) {
String error = String.format("Play Error what=%d extra=%d",what,extra);
Toast.makeText(VrPlayerActivity.this, error, Toast.LENGTH_SHORT).show();
return true;
}
});
mMediaPlayerWrapper.getPlayer().setOnVideoSizeChangedListener(new IMediaPlayer.OnVideoSizeChangedListener() {
@Override
public void onVideoSizeChanged(IMediaPlayer mp, int width, int height, int sar_num, int sar_den) {
getVRLibrary().onTextureResize(width, height);
}
});
if(videoModel.getVideoUrl()!=null){
mMediaPlayerWrapper.openRemoteFile(videoModel.getVideoUrl());
mMediaPlayerWrapper.prepare();
}
mMediaPlayerWrapper.getPlayer().setOnBufferingUpdateListener(new IMediaPlayer.OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(IMediaPlayer iMediaPlayer, int percent) {
mCurrentBufferPercentage = percent;
}
});
mMediaPlayerWrapper.getPlayer().setOnCompletionListener(new IMediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(IMediaPlayer iMediaPlayer) {
mHandler.sendEmptyMessage(MESSAGE_START_COUNTDOWN);
}
});
}
//初始化页面菜单功能
private void initView() {
img_back= (ImageView) findViewById(R.id.img_back);
img_back.setOnClickListener(this);
touchRl= (RelativeLayout) findViewById(R.id.rl_touch_menu);
touchRl.setOnClickListener(this);
verticalSeekBar= (VerticalSeekBar) findViewById(R.id.seekbar_voice);
addVoice= (RelativeLayout) findViewById(R.id.rl_voice_add);
subVoice= (RelativeLayout) findViewById(R.id.rl_voice_sub);
addVoice.setOnClickListener(this);
subVoice.setOnClickListener(this);
//不要显示进度球
verticalSeekBar.setThumbSize(1,1);
verticalSeekBar.setUnSelectColor(Color.parseColor("#ff707070"));
verticalSeekBar.setSelectColor(Color.parseColor("#ffffffff"));
//单位px
verticalSeekBar.setmInnerProgressWidth(3);
verticalSeekBar.setOnSlideChangeListener(slideChangeListener);
titleTv= (TextView) findViewById(R.id.tv_vr_title);
titleTv.setText(videoModel.getName());
btn_vr_nor= (Button) findViewById(R.id.btn_vr_nor);
btn_vr_glass= (Button) findViewById(R.id.btn_vr_glass);
btn_vr_nor.setOnClickListener(this);
btn_vr_glass.setOnClickListener(this);
img_stop_vr= (ImageView) findViewById(R.id.img_stop_vr);
img_start_vr= (ImageView) findViewById(R.id.img_start_vr);
img_stop_vr.setOnClickListener(this);
img_start_vr.setOnClickListener(this);
endTimeTv = (TextView) findViewById(R.id.tv_end_play_time);
currentTimeTv = (TextView) findViewById(R.id.tv_current_play_time);
img_next_video= (ImageView) findViewById(R.id.img_next_video);
img_next_video.setOnClickListener(this);
audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
mMaxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
verticalSeekBar.setProgress(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) * 100 / mMaxVolume);
playSeekbar= (SeekBar) findViewById(R.id.seekbar_play_video);
playSeekbar.setMax(1000);
playSeekbar.setOnSeekBarChangeListener(mSeekListener);
if(playType==MDVRLibrary.DISPLAY_MODE_GLASS){
//因为默认是360全景,所有只有当playType为眼镜模式时,才需要改变默认.在M360PlayerActivity中,createVRLibrary带不过参数,所有是先设置全景,再改变
changeToglass();
}
}
@Override
protected MDVRLibrary createVRLibrary() {
return MDVRLibrary.with(this)
.displayMode(MDVRLibrary.DISPLAY_MODE_NORMAL) //默认360度全景
.interactiveMode(MDVRLibrary.INTERACTIVE_MODE_MOTION_WITH_TOUCH)//触摸和重力
.asVideo(new MDVRLibrary.IOnSurfaceReadyCallback() {
@Override
public void onSurfaceReady(Surface surface) {
mMediaPlayerWrapper.setSurface(surface);
}
})
.ifNotSupport(new MDVRLibrary.INotSupportCallback() {
@Override
public void onNotSupport(int mode) {
String tip = mode == MDVRLibrary.INTERACTIVE_MODE_MOTION
? "onNotSupport:MOTION" : "onNotSupport:" + String.valueOf(mode);
Toast.makeText(VrPlayerActivity.this, tip, Toast.LENGTH_SHORT).show();
}
})
.pinchConfig(new MDPinchConfig().setMin(1.0f).setMax(8.0f).setDefaultValue(0.1f))
.pinchEnabled(true)
.directorFactory(new MD360DirectorFactory() {
@Override
public MD360Director createDirector(int index) {
return MD360Director.builder().setPitch(90).build();
}
})
.projectionFactory(new CustomProjectionFactory()).listenGesture(new MDVRLibrary.IGestureListener() {
@Override
public void onClick(MotionEvent e) {
touchRl.setVisibility(View.VISIBLE);
if(mMediaPlayerWrapper.getPlayer().isPlaying()){
img_start_vr.setVisibility(View.GONE);
img_stop_vr.setVisibility(View.VISIBLE);
}else {
img_start_vr.setVisibility(View.VISIBLE);
img_stop_vr.setVisibility(View.GONE);
}
mHandler.sendEmptyMessage(MESSAGE_SHOW_PROGRESS);
hasMenuAction();
verticalSeekBar.setProgress(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) * 100 / mMaxVolume);
}
})
.barrelDistortionConfig(new BarrelDistortionConfig().setDefaultEnabled(false).setScale(0.95f))
.build(findViewById(R.id.gl_view));
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeMessages(MESSAGE_SHOW_PROGRESS);
mMediaPlayerWrapper.destroy();
}
@Override
protected void onPause() {
super.onPause();
mMediaPlayerWrapper.pause();
}
@Override
protected void onResume() {
super.onResume();
// mMediaPlayerWrapper.resume();
}
int curProgress;
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.rl_touch_menu://点击菜单无点击事件的区域,菜单消失
touchRl.setVisibility(View.GONE);
mHandler.removeMessages(MESSAGE_TOUCH_MENU);
break;
case R.id.rl_voice_add://增加音量
curProgress=verticalSeekBar.getProgress();
Log.d("vrdemo","音量增加="+verticalSeekBar.getProgress());
if(curProgress<100){
curProgress=curProgress+5;
verticalSeekBar.setProgress(curProgress);
}
volume = (int) (mMaxVolume * curProgress * 0.01);
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
hasMenuAction();
break;
case R.id.rl_voice_sub://减少音量
Log.d("vrdemo","音量减少="+verticalSeekBar.getProgress());
curProgress=verticalSeekBar.getProgress();
if(curProgress>0){
curProgress=curProgress-5;
verticalSeekBar.setProgress(curProgress);
}
volume = (int) (mMaxVolume * curProgress * 0.01);
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
hasMenuAction();
break;
case R.id.btn_vr_nor://360全景模式
if(playType!=MDVRLibrary.DISPLAY_MODE_NORMAL){
changeTo360nor();
}
hasMenuAction();
break;
case R.id.btn_vr_glass://眼镜模式
if(playType!=MDVRLibrary.DISPLAY_MODE_GLASS){
changeToglass();
}
hasMenuAction();
break;
case R.id.img_stop_vr://暂停
img_stop_vr.setVisibility(View.GONE);
img_start_vr.setVisibility(View.VISIBLE);
mMediaPlayerWrapper.pause();
mHandler.removeMessages(MESSAGE_TOUCH_MENU);
break;
case R.id.img_start_vr://继续播放
img_start_vr.setVisibility(View.GONE);
img_stop_vr.setVisibility(View.VISIBLE);
mMediaPlayerWrapper.resume();
hasMenuAction();
break;
case R.id.img_back://返回
finish();
break;
case R.id.img_next_video://下一部
playNext();
break;
}
}
private void playNext() {
//获取下一步内容
//TODO 获取新片资源
if(videoModel.getId()==15){
videoModel=MainActivity.videoModels.get(0);
}else {
videoModel=MainActivity.videoModels.get(videoModel.getId());
}
titleTv.setText(videoModel.getName());
mMediaPlayerWrapper.pause();
mMediaPlayerWrapper.destroy();
mMediaPlayerWrapper.init();
mMediaPlayerWrapper.openRemoteFile(videoModel.getVideoUrl());
mMediaPlayerWrapper.prepare();
}
//切换成360全景模式
public void changeTo360nor(){
playType=MDVRLibrary.DISPLAY_MODE_NORMAL;
getVRLibrary().switchDisplayMode(VrPlayerActivity.this, MDVRLibrary.DISPLAY_MODE_NORMAL);
}
public void changeToglass(){
playType=MDVRLibrary.DISPLAY_MODE_GLASS;
getVRLibrary().switchDisplayMode(VrPlayerActivity.this, MDVRLibrary.DISPLAY_MODE_GLASS);
}
private VerticalSeekBar.SlideChangeListener slideChangeListener=new VerticalSeekBar.SlideChangeListener() {
@Override
public void onStart(VerticalSeekBar slideView, int progress) {
}
@Override
public void onProgress(VerticalSeekBar slideView, int progress) {
volume= (int) (mMaxVolume * progress * 0.01);
if (volume > mMaxVolume)
volume = mMaxVolume;
else if (volume < 0)
volume = 0;
// 变更声音
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
}
@Override
public void onStop(VerticalSeekBar slideView, int progress) {
}
};
/**
* 时长格式化显示
*/
private String generateTime(long time) {
int totalSeconds = (int) (time / 1000);
int seconds = totalSeconds % 60;
int minutes = (totalSeconds / 60) % 60;
int hours = totalSeconds / 3600;
return hours > 0 ? String.format("%02d:%02d:%02d", hours, minutes, seconds) : String.format("%02d:%02d", minutes, seconds);
}
/**
* 进度条滑动监听
*/
private final SeekBar.OnSeekBarChangeListener mSeekListener = new SeekBar.OnSeekBarChangeListener() {
/**数值的改变*/
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
long duration = mMediaPlayerWrapper.getPlayer().getDuration();
int position = (int) ((duration * progress * 1.0) / 1000);
String time = generateTime(position);
currentTimeTv.setText(time);
}
/**开始拖动*/
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
isDragging = true;
mHandler.removeMessages(MESSAGE_SHOW_PROGRESS);
}
/**停止拖动*/
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
long duration = mMediaPlayerWrapper.getPlayer().getDuration();
mMediaPlayerWrapper.getPlayer().seekTo((int) ((duration * seekBar.getProgress() * 1.0) / 1000));
mHandler.removeMessages(MESSAGE_SHOW_PROGRESS);
isDragging = false;
mHandler.sendEmptyMessageDelayed(MESSAGE_SHOW_PROGRESS, 1000);
hasMenuAction();
}
};
/**
* 消息处理
*/
private Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
/**滑动中,同步播放进度*/
case MESSAGE_SHOW_PROGRESS:
long pos = syncProgress();
if (!isDragging) {
msg = obtainMessage(MESSAGE_SHOW_PROGRESS);
sendMessageDelayed(msg, 1000 - (pos % 1000));
}
break;
/**隐藏菜单面板*/
case MESSAGE_TOUCH_MENU:
touchRl.setVisibility(View.GONE);
//防止点触面引起的显示冲突
break;
case MESSAGE_PLAY_NEXT:
playNext();
break;
case MESSAGE_START_COUNTDOWN://播放完成时,开始倒计时,抛出handler是不明确完成时是否有线程安全问题
playNext();
break;
}
}
};
/**
* 同步进度
*/
private long syncProgress() {
if (isDragging) {
return 0;
}
long position = mMediaPlayerWrapper.getPlayer().getCurrentPosition();
long duration = mMediaPlayerWrapper.getPlayer().getDuration();
if (playSeekbar != null) {
if (duration > 0) {
long pos = 1000L * position / duration;
playSeekbar.setProgress((int) pos);
}
int percent =mCurrentBufferPercentage;
playSeekbar.setSecondaryProgress(percent * 10);
}
currentTimeTv.setText(generateTime(position));
endTimeTv.setText(generateTime(duration));
verticalSeekBar.setProgress(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) * 100 / mMaxVolume);
return position;
}
//有菜单操作
public void hasMenuAction(){
if(isDragging){
mHandler.removeMessages(MESSAGE_TOUCH_MENU);
}else {
mHandler.removeMessages(MESSAGE_TOUCH_MENU);
mHandler.sendEmptyMessageDelayed(MESSAGE_TOUCH_MENU, 5000);
}
}
}
代码中用到的VideoModel是一个视频资源的model,可以根据实际项目需求传入,大致可以有标题,视频的url,封面图片,视频时长等字段。
这里讲下普通视频资源跟VR视频资源。拍摄VR视频的时候,是那种360°的机器,拍出来的视频就是360°的,如果你在浏览器上放VR视频,看到的一个很长的视频,你把VR视频用VR播放器播放,就相当于把长视频又卷到一个球上,而观看者在球中间。
demo中放置的几个测试url都是普通视频的url,所以看起来想过就很畸形了。