Vitamio简介:Vitamio是一个支持所有Android设备的多媒体框架。Vitamio与Android默认的MediaPlayer工作方式相似,但包含更加强大的功能!(注意:Vitamio商业化后个人免费、公司收费)
vitamio官网:https://www.vitamio.org
vitamio SDK地址:https://github.com/yixia/VitamioBundle
之前开发一个视频播放类的项目,需要实现在线播放的功能,找了很多视频播放框架,觉得Vitamio视频播放框架还不错,也相对稳定,但是在网上找了很多教程都少之又少,Vitamio官网写的教程也不是很清晰,所以就自己就把Vitamio在github上的demo进行研究,花了点时间写了个demo出来并且将Vitamio的视频控制器界面进行自定义,支持视频亮度、音量的调节,话不多说,先上效果图。
附上 Demo的github地址
自定义视频控制器
目前,作者只实现了自定义手势调节亮度、音量的加减以及播放控制的功能,更多的功能等待着大家一起去挖掘。
使用步骤:
1.引入vitamio SDK的方式有两种:
(1)直接以module的方式引入
(2)通过Complie的方式引入
作者是采用的第一种方式,下面是一些需要注意的地方:
- 清单文件配置:
1)权限设置:
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
2)application配置:
<!-- 必须初始化 -->
<activity
android:name="io.vov.vitamio.activity.InitActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
android:launchMode="singleTop"
android:theme="@android:style/Theme.NoTitleBar"
android:windowSoftInputMode="stateAlwaysHidden" />
2.主要代码:
- 播放视频代码
package com.stx.vitamiodemo;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ProgressBar;
import android.widget.TextView;
import io.vov.vitamio.LibsChecker;
import io.vov.vitamio.MediaPlayer;
import io.vov.vitamio.Vitamio;
import io.vov.vitamio.widget.MediaController;
import io.vov.vitamio.widget.VideoView;
/**
* Vitamio视频播放框架Demo
*/
public class MainActivity extends AppCompatActivity implements MediaPlayer.OnInfoListener,
MediaPlayer.OnBufferingUpdateListener{
//视频地址
private String path = "http://baobab.wdjcdn.com/145076769089714.mp4";
private Uri uri;
private ProgressBar pb;
private TextView downloadRateView, loadRateView;
private CustomMediaController mCustomMediaController;
private VideoView mVideoView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//定义全屏参数
int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN;
//获得当前窗体对象
Window window = MainActivity.this.getWindow();
//设置当前窗体为全屏显示
window.setFlags(flag, flag);
//必须写这个,初始化加载库文件
Vitamio.initialize(this);
//设置视频解码监听
if (!LibsChecker.checkVitamioLibs(this)) {
return;
}
setContentView(R.layout.activity_main);
initView();
initData();
//获取上一次保存的进度
long progress = SPUtils.getLong(this, "progress");//此处使用SharedPreferences保存当前播放进度,SPUtils是我自己封装的工具类
mVideoView.seekTo(progress);
mVideoView.start();
}
//初始化控件
private void initView() {
mVideoView = (VideoView) findViewById(R.id.buffer);
mCustomMediaController=new CustomMediaController(this,mVideoView,this);
mCustomMediaController.setVideoName("白火锅 x 红火锅");
pb = (ProgressBar) findViewById(R.id.probar);
downloadRateView = (TextView) findViewById(R.id.download_rate);
loadRateView = (TextView) findViewById(R.id.load_rate);
}
//初始化数据
private void initData() {
uri = Uri.parse(path);
mVideoView.setVideoURI(uri);//设置视频播放地址
mCustomMediaController.show(5000);
mVideoView.setMediaController(mCustomMediaController);
mVideoView.setVideoQuality(MediaPlayer.VIDEOQUALITY_HIGH);//高画质
mVideoView.requestFocus();
mVideoView.setOnInfoListener(this);
mVideoView.setOnBufferingUpdateListener(this);
mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mediaPlayer) {
mediaPlayer.setPlaybackSpeed(1.0f);
}
});
}
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
switch (what) {
case MediaPlayer.MEDIA_INFO_BUFFERING_START:
if (mVideoView.isPlaying()) {
mVideoView.pause();
pb.setVisibility(View.VISIBLE);
downloadRateView.setText("");
loadRateView.setText("");
downloadRateView.setVisibility(View.VISIBLE);
loadRateView.setVisibility(View.VISIBLE);
}
break;
case MediaPlayer.MEDIA_INFO_BUFFERING_END:
mVideoView.start();
pb.setVisibility(View.GONE);
downloadRateView.setVisibility(View.GONE);
loadRateView.setVisibility(View.GONE);
break;
case MediaPlayer.MEDIA_INFO_DOWNLOAD_RATE_CHANGED:
downloadRateView.setText("" + extra + "kb/s" + " ");
break;
}
return true;
}
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
loadRateView.setText(percent + "%");
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
//屏幕切换时,设置全屏
if (mVideoView != null){
mVideoView.setVideoLayout(VideoView.VIDEO_LAYOUT_SCALE, 0);
}
super.onConfigurationChanged(newConfig);
}
@Override
protected void onPause() {
super.onPause();
//保存进度
SPUtils.putLong(this,"progress",mVideoView.getCurrentPosition());
}
}
- 自定义视频控制器:
主要实现了手势调节视频亮度、音量的加减控制。
package com.stx.vitamiodemo;
import android.app.Activity;
import android.content.Context;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
import android.view.Display;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import io.vov.vitamio.widget.MediaController;
import io.vov.vitamio.widget.VideoView;
/**
* Created by xhb on 2016/3/1.
* 自定义视频控制器
*/
public class CustomMediaController extends MediaController {
private static final int HIDEFRAM = 0;//控制提示窗口的显示
private GestureDetector mGestureDetector;
private ImageButton img_back;//返回按钮
private TextView mFileName;//文件名
private VideoView videoView;
private Activity activity;
private Context context;
private String videoname;//视频名称
private int controllerWidth = 0;//设置mediaController高度为了使横屏时top显示在屏幕顶端
private View mVolumeBrightnessLayout;//提示窗口
private ImageView mOperationBg;//提示图片
private TextView mOperationTv;//提示文字
private AudioManager mAudioManager;
private SeekBar progress;
private boolean mDragging;
private MediaPlayerControl player;
//最大声音
private int mMaxVolume;
// 当前声音
private int mVolume = -1;
//当前亮度
private float mBrightness = -1f;
//返回监听
private View.OnClickListener backListener = new View.OnClickListener() {
public void onClick(View v) {
if (activity != null) {
activity.finish();
}
}
};
private Handler myHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
long pos;
switch (msg.what) {
case HIDEFRAM://隐藏提示窗口
mVolumeBrightnessLayout.setVisibility(View.GONE);
mOperationTv.setVisibility(View.GONE);
break;
}
}
};
//videoview 用于对视频进行控制的等,activity为了退出
public CustomMediaController(Context context, VideoView videoView, Activity activity) {
super(context);
this.context = context;
this.videoView = videoView;
this.activity = activity;
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
controllerWidth = wm.getDefaultDisplay().getWidth();
mGestureDetector = new GestureDetector(context, new MyGestureListener());
}
@Override
protected View makeControllerView() {
//此处的 mymediacontroller 为我们自定义控制器的布局文件名称
View v = ((LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate
(getResources().getIdentifier("mymediacontroller", "layout", getContext().getPackageName()), this);
v.setMinimumHeight(controllerWidth);
//获取控件
img_back = (ImageButton) v.findViewById(getResources().getIdentifier("mediacontroller_top_back", "id",
context.getPackageName()));
mFileName = (TextView) v.findViewById(getResources().getIdentifier("mediacontroller_filename", "id",
context.getPackageName()));
if (mFileName != null) {
mFileName.setText(videoname);
}
//声音控制
mVolumeBrightnessLayout = (RelativeLayout) v.findViewById(R.id.operation_volume_brightness);
mOperationBg = (ImageView) v.findViewById(R.id.operation_bg);
mOperationTv = (TextView) v.findViewById(R.id.operation_tv);
mOperationTv.setVisibility(View.GONE);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mMaxVolume = mAudioManager
.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
//注册事件监听
img_back.setOnClickListener(backListener);
return v;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
System.out.println("MYApp-MyMediaController-dispatchKeyEvent");
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mGestureDetector.onTouchEvent(event)) return true;
// 处理手势结束
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_UP:
endGesture();
break;
}
return super.onTouchEvent(event);
}
/**
* 手势结束
*/
private void endGesture() {
mVolume = -1;
mBrightness = -1f;
// 隐藏
myHandler.removeMessages(HIDEFRAM);
myHandler.sendEmptyMessageDelayed(HIDEFRAM, 1);
}
private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
/**
* 因为使用的是自定义的mediaController 当显示后,mediaController会铺满屏幕,
* 所以VideoView的点击事件会被拦截,所以重写控制器的手势事件,
* 将全部的操作全部写在控制器中,
* 因为点击事件被控制器拦截,无法传递到下层的VideoView,
* 所以 原来的单机隐藏会失效,作为代替,
* 在手势监听中onSingleTapConfirmed()添加自定义的隐藏/显示,
*
* @param e
* @return
*/
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
//当手势结束,并且是单击结束时,控制器隐藏/显示
toggleMediaControlsVisiblity();
return super.onSingleTapConfirmed(e);
}
@Override
public boolean onDown(MotionEvent e) {
return true;
}
//滑动事件监听
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
float mOldX = e1.getX(), mOldY = e1.getY();
int y = (int) e2.getRawY();
int x = (int) e2.getRawX();
Display disp = activity.getWindowManager().getDefaultDisplay();
int windowWidth = disp.getWidth();
int windowHeight = disp.getHeight();
if (mOldX > windowWidth * 3.0 / 4.0) {// 右边滑动 屏幕 3/4
onVolumeSlide((mOldY - y) / windowHeight);
} else if (mOldX < windowWidth * 1.0 / 4.0) {// 左边滑动 屏幕 1/4
onBrightnessSlide((mOldY - y) / windowHeight);
}
return super.onScroll(e1, e2, distanceX, distanceY);
}
@Override
public boolean onDoubleTap(MotionEvent e) {
playOrPause();
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return super.onFling(e1, e2, velocityX, velocityY);
}
}
/**
* 滑动改变声音大小
*
* @param percent
*/
private void onVolumeSlide(float percent) {
if (mVolume == -1) {
mVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
if (mVolume < 0)
mVolume = 0;
// 显示
mVolumeBrightnessLayout.setVisibility(View.VISIBLE);
mOperationTv.setVisibility(VISIBLE);
}
int index = (int) (percent * mMaxVolume) + mVolume;
if (index > mMaxVolume)
index = mMaxVolume;
else if (index < 0)
index = 0;
if (index >= 10) {
mOperationBg.setImageResource(R.drawable.volmn_100);
} else if (index >= 5 && index < 10) {
mOperationBg.setImageResource(R.drawable.volmn_60);
} else if (index > 0 && index < 5) {
mOperationBg.setImageResource(R.drawable.volmn_30);
} else {
mOperationBg.setImageResource(R.drawable.volmn_no);
}
//DecimalFormat df = new DecimalFormat("######0.00");
mOperationTv.setText((int) (((double) index / mMaxVolume) * 100) + "%");
// 变更声音
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, index, 0);
}
/**
* 滑动改变亮度
*
* @param percent
*/
private void onBrightnessSlide(float percent) {
if (mBrightness < 0) {
mBrightness = activity.getWindow().getAttributes().screenBrightness;
if (mBrightness <= 0.00f)
mBrightness = 0.50f;
if (mBrightness < 0.01f)
mBrightness = 0.01f;
// 显示
mVolumeBrightnessLayout.setVisibility(View.VISIBLE);
mOperationTv.setVisibility(VISIBLE);
}
WindowManager.LayoutParams lpa = activity.getWindow().getAttributes();
lpa.screenBrightness = mBrightness + percent;
if (lpa.screenBrightness > 1.0f)
lpa.screenBrightness = 1.0f;
else if (lpa.screenBrightness < 0.01f)
lpa.screenBrightness = 0.01f;
activity.getWindow().setAttributes(lpa);
mOperationTv.setText((int) (lpa.screenBrightness * 100) + "%");
if (lpa.screenBrightness * 100 >= 90) {
mOperationBg.setImageResource(R.drawable.light_100);
} else if (lpa.screenBrightness * 100 >= 80 && lpa.screenBrightness * 100 < 90) {
mOperationBg.setImageResource(R.drawable.light_90);
} else if (lpa.screenBrightness * 100 >= 70 && lpa.screenBrightness * 100 < 80) {
mOperationBg.setImageResource(R.drawable.light_80);
} else if (lpa.screenBrightness * 100 >= 60 && lpa.screenBrightness * 100 < 70) {
mOperationBg.setImageResource(R.drawable.light_70);
} else if (lpa.screenBrightness * 100 >= 50 && lpa.screenBrightness * 100 < 60) {
mOperationBg.setImageResource(R.drawable.light_60);
} else if (lpa.screenBrightness * 100 >= 40 && lpa.screenBrightness * 100 < 50) {
mOperationBg.setImageResource(R.drawable.light_50);
} else if (lpa.screenBrightness * 100 >= 30 && lpa.screenBrightness * 100 < 40) {
mOperationBg.setImageResource(R.drawable.light_40);
} else if (lpa.screenBrightness * 100 >= 20 && lpa.screenBrightness * 100 < 20) {
mOperationBg.setImageResource(R.drawable.light_30);
} else if (lpa.screenBrightness * 100 >= 10 && lpa.screenBrightness * 100 < 20) {
mOperationBg.setImageResource(R.drawable.light_20);
}
}
/**
* 设置视频文件名
*
* @param name
*/
public void setVideoName(String name) {
videoname = name;
if (mFileName != null) {
mFileName.setText(name);
}
}
/**
* 隐藏或显示
*/
private void toggleMediaControlsVisiblity() {
if (isShowing()) {
hide();
} else {
show();
}
}
/**
* 播放/暂停
*/
private void playOrPause() {
if (videoView != null)
if (videoView.isPlaying()) {
videoView.pause();
} else {
videoView.start();
}
}
}
- 自定义控制器布局文件:
<?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:background="@drawable/video_player_bg_color"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:background="#77000000"
android:layout_height="34dp">
<ImageButton
android:id="@+id/mediacontroller_top_back"
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:background="@null"
android:src="@drawable/ic_player_close_white"/>
<TextView
android:id="@+id/mediacontroller_filename"
style="@style/MediaController_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:layout_toRightOf="@+id/mediacontroller_top_back"
android:ellipsize="marquee"
android:singleLine="true"
android:text="file name"/>
<ImageButton
android:id="@+id/mediacontroller_share"
android:layout_width="50dp"
android:layout_height="match_parent"
android:background="@null"
android:src="@drawable/ic_action_share_without_padding"
android:layout_alignParentRight="true"/>
<ImageButton
android:id="@+id/mediacontroller_favorite"
android:layout_width="50dp"
android:layout_height="match_parent"
android:background="@null"
android:layout_toLeftOf="@id/mediacontroller_share"
android:src="@drawable/ic_action_favorites"/>
</RelativeLayout>
<ImageButton
android:id="@+id/mediacontroller_play_pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/paly_selector"
android:background="@null"/>
<RelativeLayout
android:id="@+id/operation_volume_brightness"
android:layout_width="150dp"
android:layout_height="75dp"
android:layout_centerInParent="true"
android:background="@drawable/videobg"
android:orientation="horizontal"
android:padding="0dip"
android:visibility="gone">
<ImageView
android:id="@+id/operation_bg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@drawable/video_volumn_bg"/>
<TextView
android:id="@+id/operation_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/operation_bg"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom ="true"
android:text="32:22/45:00"
android:textColor="#ffffff"
android:textSize="10sp"
android:visibility="gone"
/>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_alignParentBottom="true"
android:background="#77000000"
android:layout_height="50dp">
<TextView
android:id="@+id/mediacontroller_time_current"
style="@style/MediaController_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="15dp"
android:text="33:33:33"
/>
<TextView
android:id="@+id/mediacontroller_time_total"
style="@style/MediaController_Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="15dp"
android:text="33:33:33"/>
<SeekBar
android:id="@+id/mediacontroller_seekbar"
style="@style/MediaController_SeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/mediacontroller_time_total"
android:layout_toRightOf="@id/mediacontroller_time_current"
android:focusable="true"
android:max="1000"/>
</RelativeLayout>
</RelativeLayout>
</LinearLayout>
- 视频播放界面布局:
<?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="match_parent">
<io.vov.vitamio.widget.CenterLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<io.vov.vitamio.widget.VideoView
android:id="@+id/buffer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true" />
</io.vov.vitamio.widget.CenterLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="horizontal">
<ProgressBar
android:id="@+id/probar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="50dp"
android:layout_height="50dp" />
<TextView
android:id="@+id/download_rate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#FFFFFF"
android:text="" />
<TextView
android:id="@+id/load_rate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#FFFFFF"
android:text="" />
</LinearLayout>
</RelativeLayout>
解决部分视频不能全屏播放###
播放界面的布局如下,即可拉伸视频,主要不要嵌套vitamio自定义的CenterLayout布局
<!--解决部分视频视频由于分辨率问题,不能全屏播放,在此处不要嵌套vitamio的CenterLayout布局-->
<!--<io.vov.vitamio.widget.CenterLayout-->
<!--android:layout_width="match_parent"-->
<!--android:layout_height="match_parent"-->
<!--android:orientation="vertical">-->
<io.vov.vitamio.widget.VideoView
android:id="@+id/buffer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"/>
<!--</io.vov.vitamio.widget.CenterLayout>-->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="horizontal">
<ProgressBar
android:id="@+id/probar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="50dp"
android:layout_height="50dp"/>
<TextView
android:id="@+id/download_rate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text=""
android:textColor="#FFFFFF"/>
<TextView
android:id="@+id/load_rate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text=""
android:textColor="#FFFFFF"/>
</LinearLayout>
</RelativeLayout>
github地址
写的不好之处还望大家见谅。、
网友评论
http://hehe.heheedu.com/uploadFiles/resources/2017/09/14/0f2eaba594f845ebad9bd5305bcecbec.mp4
建议使用ijk 吧
这里有没有不全屏的参数啊
我是想让窗体和视频一样大小
return;
}
报的是 LibsChecker找不到,可我导入的module 是官网下载的最新的呀
Error:smallestScreenSize|keyboard|keyboardHidden|navigation).
我按照说明配置完了,为什么报这个错误啊?
MediaController mMediaController;
CustomMediaController mCustomMediaController;
为什么需要两个Controller,它们各自的职责是什么啊。不太理解,谢谢
mWindow.showAtLocation(mAnchor, Gravity.NO_GRAVITY, anchorRect.left, anchorRect.bottom);
2.横竖屏切换的问题,可以在切换的时候保存一下当前的播放进度,切换过去时再将进度获取到。