Vitamio簡介:Vitamio是一個(gè)支持所有Android設(shè)備的多媒體框架。Vitamio與Android默認(rèn)的MediaPlayer工作方式相似,但包含更加強(qiáng)大的功能!(注意:Vitamio商業(yè)化后個(gè)人免費(fèi)、公司收費(fèi))
vitamio官網(wǎng):https://www.vitamio.org
vitamio SDK地址:https://github.com/yixia/VitamioBundle
之前開發(fā)一個(gè)視頻播放類的項(xiàng)目,需要實(shí)現(xiàn)在線播放的功能,找了很多視頻播放框架,覺得Vitamio視頻播放框架還不錯(cuò),也相對(duì)穩(wěn)定,但是在網(wǎng)上找了很多教程都少之又少,Vitamio官網(wǎng)寫的教程也不是很清晰,所以就自己就把Vitamio在github上的demo進(jìn)行研究,花了點(diǎn)時(shí)間寫了個(gè)demo出來并且將Vitamio的視頻控制器界面進(jìn)行自定義,支持視頻亮度、音量的調(diào)節(jié),話不多說,先上效果圖。
附上 Demo的github地址
自定義視頻控制器
目前,作者只實(shí)現(xiàn)了自定義手勢調(diào)節(jié)亮度、音量的加減以及播放控制的功能,更多的功能等待著大家一起去挖掘。
使用步驟:
1.引入vitamio SDK的方式有兩種:
(1)直接以module的方式引入
(2)通過Complie的方式引入
作者是采用的第一種方式,下面是一些需要注意的地方:
- 清單文件配置:
1)權(quán)限設(shè)置:
<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);
//定義全屏參數(shù)
int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN;
//獲得當(dāng)前窗體對(duì)象
Window window = MainActivity.this.getWindow();
//設(shè)置當(dāng)前窗體為全屏顯示
window.setFlags(flag, flag);
//必須寫這個(gè),初始化加載庫文件
Vitamio.initialize(this);
//設(shè)置視頻解碼監(jiān)聽
if (!LibsChecker.checkVitamioLibs(this)) {
return;
}
setContentView(R.layout.activity_main);
initView();
initData();
//獲取上一次保存的進(jìn)度
long progress = SPUtils.getLong(this, "progress");//此處使用SharedPreferences保存當(dāng)前播放進(jìn)度,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);
}
//初始化數(shù)據(jù)
private void initData() {
uri = Uri.parse(path);
mVideoView.setVideoURI(uri);//設(shè)置視頻播放地址
mCustomMediaController.show(5000);
mVideoView.setMediaController(mCustomMediaController);
mVideoView.setVideoQuality(MediaPlayer.VIDEOQUALITY_HIGH);//高畫質(zhì)
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) {
//屏幕切換時(shí),設(shè)置全屏
if (mVideoView != null){
mVideoView.setVideoLayout(VideoView.VIDEO_LAYOUT_SCALE, 0);
}
super.onConfigurationChanged(newConfig);
}
@Override
protected void onPause() {
super.onPause();
//保存進(jìn)度
SPUtils.putLong(this,"progress",mVideoView.getCurrentPosition());
}
}
- 自定義視頻控制器:
主要實(shí)現(xiàn)了手勢調(diào)節(jié)視頻亮度、音量的加減控制。
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;//設(shè)置mediaController高度為了使橫屏?xí)rtop顯示在屏幕頂端
private View mVolumeBrightnessLayout;//提示窗口
private ImageView mOperationBg;//提示圖片
private TextView mOperationTv;//提示文字
private AudioManager mAudioManager;
private SeekBar progress;
private boolean mDragging;
private MediaPlayerControl player;
//最大聲音
private int mMaxVolume;
// 當(dāng)前聲音
private int mVolume = -1;
//當(dāng)前亮度
private float mBrightness = -1f;
//返回監(jiān)聽
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 用于對(duì)視頻進(jìn)行控制的等,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);
//注冊(cè)事件監(jiān)聽
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;
// 處理手勢結(jié)束
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_UP:
endGesture();
break;
}
return super.onTouchEvent(event);
}
/**
* 手勢結(jié)束
*/
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;
}
/**
* 因?yàn)槭褂玫氖亲远x的mediaController 當(dāng)顯示后,mediaController會(huì)鋪滿屏幕,
* 所以VideoView的點(diǎn)擊事件會(huì)被攔截,所以重寫控制器的手勢事件,
* 將全部的操作全部寫在控制器中,
* 因?yàn)辄c(diǎn)擊事件被控制器攔截,無法傳遞到下層的VideoView,
* 所以 原來的單機(jī)隱藏會(huì)失效,作為代替,
* 在手勢監(jiān)聽中onSingleTapConfirmed()添加自定義的隱藏/顯示,
*
* @param e
* @return
*/
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
//當(dāng)手勢結(jié)束,并且是單擊結(jié)束時(shí),控制器隱藏/顯示
toggleMediaControlsVisiblity();
return super.onSingleTapConfirmed(e);
}
@Override
public boolean onDown(MotionEvent e) {
return true;
}
//滑動(dòng)事件監(jiān)聽
@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) {// 右邊滑動(dòng) 屏幕 3/4
onVolumeSlide((mOldY - y) / windowHeight);
} else if (mOldX < windowWidth * 1.0 / 4.0) {// 左邊滑動(dòng) 屏幕 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);
}
}
/**
* 滑動(dòng)改變聲音大小
*
* @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);
}
/**
* 滑動(dòng)改變亮度
*
* @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);
}
}
/**
* 設(shè)置視頻文件名
*
* @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地址
寫的不好之處還望大家見諒。、