添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
大方的皮蛋  ·  Quantitative ...·  5 月前    · 
坚强的香蕉  ·  What will the Space ...·  8 月前    · 
健身的冲锋衣  ·  Narrow Vs. Broad ...·  9 月前    · 

在之前的博客中,我分析过本地音乐播放器的逻辑和写法,需要使用MediaPlayer类,对于多媒体音频可进行播放,暂停,切换,停止等操作。在本篇博客中,将继续使用MediaPlayer类,将其放置于SurfaceView上进行视频播放。

SurfaceView的介绍

直接从内存或者DMA等硬件接口取得图像数据,是个非常重要的绘图容器。
它的特性是:可以在主线程之外的线程中向屏幕绘图上。这样可以避免画图任务繁重的时候造成主线程阻塞,从而提高了程序的反应速度。在游戏开发中多用到SurfaceView,游戏中的背景、人物、动画等等尽量在画布canvas中画出。

surfaceview的核心在于提供了两个线程:UI线程和渲染线程。这里应注意:
1> 所有SurfaceView和SurfaceHolder.Callback的方法都应该在UI线程里调用, 一般来说就是应用程序主线程。渲染线程所要访问的各种变量应该作同步处理。
2> 由于surface可能被销毁,它只在SurfaceHolder.Callback.surfaceCreated()和SurfaceHolder.Callback.surfaceDestroyed()之间有效,所以要确保渲染线程访问的是合法有效的surface。

实现方法:

首先继承SurfaceView并实现SurfaceHolder.Callback接口
使用接口的原因:因为使用SurfaceView 有一个原则,所有的绘图工作必须得在Surface 被创建之后才能开始(Surface—表面,这个概念在 图形编程中常常被提到。基本上我们可以把它当作显存的一个映射,写入到Surface的内容,可以被直接复制到显存从而显示出来,这使得显示速度会非常快),而在Surface 被销毁之前必须结束。所以Callback 中的surfaceCreated 和surfaceDestroyed 就成了绘图处理代码的边界。

需要重写的方法

(1)public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}

//在surface的大小发生改变时激发

(2)public void surfaceCreated(SurfaceHolder holder){}

//在创建时激发,一般在这里调用画图的线程。

(3)public void surfaceDestroyed(SurfaceHolder holder) {}

//销毁时激发,一般在这里将画图的线程停止、释放。

整个过程:继承SurfaceView并实现SurfaceHolder.Callback接口 ----> SurfaceView.getHolder()获得SurfaceHolder对象
---->SurfaceHolder.addCallback(callback)添加回调函数---->SurfaceHolder.lockCanvas()获得Canvas对象并锁定画布
----> Canvas绘画 ---->SurfaceHolder.unlockCanvasAndPost(Canvas canvas)结束锁定画图,并提交改变,将图形显示。

如何使用SurfaceView加载MediaPlayer呢?

使用原生播放器播放res/raw当中的视频文件,采用SurfaceView加载MediaPlayer的方式。

首先绘制布局activity_media_surface.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:id="@+id/activity_media_surface"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.qianfeng.day08.MediaSurfaceActivity">
    <SurfaceView
        android:id="@+id/id_surfaceview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

对应的MediaSurfaceActivity代码为:

import android.media.MediaPlayer;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.IOException;
public class MediaSurfaceActivity extends AppCompatActivity {
    private SurfaceView mSurfaceView;
    private MediaPlayer mPlayer;
    private SurfaceHolder holder;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_media_surface);
        initView();
        setVideo();
        setListener();
    private void initView(){
        mSurfaceView = (SurfaceView) findViewById(R.id.id_surfaceview);
        mPlayer = new MediaPlayer();
        holder = mSurfaceView.getHolder();
    private void setVideo(){
        holder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                openVideo();
            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                mPlayer.reset();
                mPlayer.release();
                mPlayer = null;
    private void openVideo(){
        //重置播放器,避免在切换视频的地址时报错
        mPlayer.reset();
        Uri uri = Uri.parse("android.resource://"+getPackageName()+"/"+R.raw.video_test);
        try {
            mPlayer.setDataSource(this,uri);    //设置视频播放的地址
            //给播放器设置holder,确认在哪个播放器上进行播放
            mPlayer.setDisplay(holder);
            //开始准备播放视频
            mPlayer.prepareAsync();    //异步线程播放
        } catch (IOException e) {
            e.printStackTrace();
    private void setListener(){
        mPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                mPlayer.start();   //开始播放
    @Override
    protected void onStop() {
        super.onStop();
        mPlayer.stop();

然后就可以播放本地音乐了。接下来我们采用列表的形式播放网络资源音乐。

网络资源为:http://m2.qiushibaike.com/article/list/video?page=2&count=30&readarticles=[115762484,115762135,115764350,115761463,115760316,115764445,115763537,115758684]&rqcnt=17&r=804df97a1459411164081

分析网络json数据的格式,整体格式与每一个item的格式如下图所示:

根据此json数据的格式,完成对应的bean类的构建,并使用Gson的jar包对于json数据进行解析,并生成所需的数据源:

package com.animee.headlines;
import com.google.gson.Gson;
import java.util.List;
public class ParseVideoBean {
    private List<VideoBean>items;
    public List<VideoBean> getItems() {
        return items;
    public void setItems(List<VideoBean> items) {
        this.items = items;
    /** 解析json数据,返回数据源*/
    public static List<VideoBean>parseData(String json){
        return  new Gson().fromJson(json,ParseVideoBean.class).getItems();
    public class VideoBean{
        private String high_url;
        private String pic_url;
        private String content;
        private String low_url;
        public VideoBean(String high_url, String pic_url, String content, String low_url) {
            this.high_url = high_url;
            this.pic_url = pic_url;
            this.content = content;
            this.low_url = low_url;
        public VideoBean() { }
        public String getHigh_url() {
            return high_url;
        public void setHigh_url(String high_url) {
            this.high_url = high_url;
        public String getPic_url() {
            return pic_url;
        public void setPic_url(String pic_url) {
            this.pic_url = pic_url;
        public String getContent() {
            return content;
        public void setContent(String content) {
            this.content = content;
        public String getLow_url() {
            return low_url;
        public void setLow_url(String low_url) {
            this.low_url = low_url;

然后按照要求绘制activity对应的布局界面,以及列表视图中每一个item的布局。

activity_list_video.xml布局为:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_list_video"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ListView
        android:id="@+id/id_lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

item_video.xml布局为:

<?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">
    <SurfaceView
        android:id="@+id/id_item_surface"
        android:layout_width="match_parent"
        android:layout_height="220dp" />
    <ImageView
        android:id="@+id/id_item_thumb"
        android:layout_width="match_parent"
        android:layout_height="220dp"
        android:scaleType="centerCrop"
        android:src="@mipmap/ic_launcher"/>
    <TextView
        android:id="@+id/id_item_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="显示内容"
        android:textSize="26sp"
        android:textColor="@color/colorAccent"
        android:layout_margin="10dp"/>
</RelativeLayout>

接下来,我们绘制listview的适配器,使用surfaceview放置mediaplayer,播放视频,显示标题文字。

import android.content.Context;
import android.media.MediaPlayer;
import android.net.Uri;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import java.io.IOException;
import java.util.List;
public class VideoListAdapter extends BaseAdapter implements View.OnClickListener{
    private Context context;
    private LayoutInflater inflater;
    private List<ParseVideoBean.VideoBean>mDatas;
    private MediaPlayer mediaPlayer;
    //明确在整个列表中,同一时间只有一个会被播放,所以一个mediaplayer就够了
    private int mCurrentPosition = -1;
    public VideoListAdapter(Context context, List<ParseVideoBean.VideoBean> mDatas) {
        this.context = context;
        this.mDatas = mDatas;
        inflater = LayoutInflater.from(context);
        mediaPlayer = new MediaPlayer();
        mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                mp.start();
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                mCurrentPosition = -1;
                notifyDataSetChanged();   //当前视频播放结束了,原来需要播放的不播放了,就通知adapter更新
    @Override
    public int getCount() {
        return mDatas.size();
    @Override
    public Object getItem(int position) {
        return mDatas.get(position);
    @Override
    public long getItemId(int position) {
        return position;
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
       ViewHolder viewHolder = null;
        if (convertView==null) {
            convertView = inflater.inflate(R.layout.item_video,parent,false);
            viewHolder = new ViewHolder(convertView);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        ParseVideoBean.VideoBean videoBean = mDatas.get(position);
        viewHolder.mContentTv.setText(videoBean.getContent());
        if (!TextUtils.isEmpty(videoBean.getPic_url())) {
            Picasso.with(context).load(videoBean.getPic_url()).into(viewHolder.mDisplayIv);
        //明确surfaceview播放的条件,播放的位置和当前绘制的位置相等,当前绘制的位置就显示surfaceview
        //并且播放mediaplayer,然后隐藏图片,如果不相等,就隐藏surfaceview不播放
        //播放的位置怎么改变,怎么获取
        //点击图片,然后被点击图片的位置就是要播放视频的位置
        viewHolder.mDisplayIv.setOnClickListener(this);
        //怎么传递当前的位置呢?可以给每个图片设置tag
        viewHolder.mDisplayIv.setTag(position);
        //判断当前位置是显示图片缩略图还是显示视频,就需要看视频播放的位置和当前的位置是否相等。
        if (mCurrentPosition==position) {   //这个位置需要播放视频
            String low_url= videoBean.getLow_url();
            viewHolder.mSurfaceView.setVisibility(View.VISIBLE);
            viewHolder.mDisplayIv.setVisibility(View.INVISIBLE);
            //判断当前位置是否播放,如果播放就暂停
            if (mediaPlayer.isPlaying()) {
                mediaPlayer.stop();
            //因为视频播放的地址发生变化了,需要重置播放器
            mediaPlayer.reset();
            //获取当前surfaceview的surfaceholder对象
            SurfaceHolder surfaceHolder = viewHolder.mSurfaceView.getHolder();
            mediaPlayer.setDisplay(surfaceHolder);
            try {
                mediaPlayer.setDataSource(context, Uri.parse(low_url));
                mediaPlayer.prepareAsync();
            } catch (IOException e) {
                e.printStackTrace();
        }else {   //不播放视频,显示图片
            viewHolder.mDisplayIv.setVisibility(View.VISIBLE);
            viewHolder.mSurfaceView.setVisibility(View.INVISIBLE);
        return convertView;
    @Override
    public void onClick(View v) {
        //哪个图片被点击了,哪个图片的点击事件就会被触发,可以拿到点击的位置
        int pos= (Integer) v.getTag();
        if (pos!=-1) {
            mCurrentPosition = pos;
            notifyDataSetChanged();
            //因为点击之后,点击位置的视频播放了,有的视频停止了,列表视图改变了,所以要提示更新
    class ViewHolder{
        SurfaceView mSurfaceView;
        ImageView mDisplayIv;
        TextView mContentTv;
        public ViewHolder(View itemView){
            mSurfaceView = (SurfaceView)itemView.findViewById(R.id.id_item_surface);
            mDisplayIv = (ImageView)itemView.findViewById(R.id.id_item_thumb);
            mContentTv = (TextView)itemView.findViewById(R.id.id_item_tv);

最后编写ListVideoActivity的代码:

import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.widget.ListView;
import android.widget.Toast;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class ListVideoActivity extends AppCompatActivity {
    private ListView mListView;
    private VideoListAdapter adapter;
    private List<ParseVideoBean.VideoBean>mDatas;
    private ProgressDialog dialog;   // 进度对话框
//  视频网络地址
    String VIDEO_PATH = "http://m2.qiushibaike.com/article/list/video?page=2&count=30&readarticles=[115762484,115762135,115764350,115761463,115760316,115764445,115763537,115758684]&rqcnt=17&r=804df97a1459411164081";
    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 100) {
//              取消对话框
                dialog.dismiss();
                String s = (String) msg.obj;  // 接收数据
                if (!TextUtils.isEmpty(s)) {   //判断接收到的数据是否为空
                    // 解析数据
                    List<ParseVideoBean.VideoBean>list = ParseVideoBean.parseData(s);
                    if (list!=null&&list.size()!=0) {
                        mDatas.addAll(list);   // 添加解析数据到数据源
                        adapter.notifyDataSetChanged();   // 提示适配器更新
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_list_video);
        mListView = (ListView) findViewById(R.id.id_lv);
        //  数据源
        mDatas = new ArrayList<>();
        // 设置适配器
        adapter = new VideoListAdapter(this,mDatas);
        mListView.setAdapter(adapter);
        initDialog();
        setData(VIDEO_PATH);
    /**对话框初始化*/
    private void initDialog(){
        dialog = new ProgressDialog(this);
        dialog.setMessage("正在加载中.....");
        dialog.setTitle("提示信息");
     *  @des 获取网络数据的方法
    private void setData(final String path){
        dialog.show();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 获取网络数据(原生写法)
                    HttpURLConnection conn = (HttpURLConnection) new URL(path).openConnection();
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    InputStream is = conn.getInputStream();
                    int hasRead = 0;
                    byte[]buf = new byte[1024];
                    while((hasRead = is.read(buf))!=-1){
                        baos.write(buf,0,hasRead);
                    String json = baos.toString();
                    Message msg = handler.obtainMessage();
                    msg.what = 100;
                    msg.obj = json;
                    handler.sendMessage(msg);
                } catch (Exception e) {
                    // 如果不能获取数据,提示用户错误
                    dialog.dismiss();
                    Toast.makeText(ListVideoActivity.this,"网络加载失败,请检查网络!!",Toast.LENGTH_LONG).show();
                    e.printStackTrace();
        }).start();

最后要记得在清单文件当中添加网络权限~

<uses-permission android:name="android.permission.INTERNET"/>

效果图如下:

写到这里,原生的视频播放器就完成了,编写后发现,逻辑代码较多,而且在功能顺畅性上也有很大的改进空间,后面我将使用第三方视频播放控件,对于此例子进行重新编写。感谢您的阅读~

点击下载相关编码

一、Android网络编程简介        我们在平时的开发中,不可避免的会使用到网络编程方面的知识;在我的理解中,App好比是一个壳,它的作用是将用户和服务器联系起来,用户通过操作App向服务器发送或者接收数据,并且以友好的方式反馈给用户,而App要和服务器交互数据,就会设计到网络编程。在Android App的开发中,用到最多的通信方式是基于Http协议,如果涉及到消息推送,可能会使用到W... 视频播放器:Video Player是最简单、高效的手机视频文件播放器。VideoPlayer可以支持大部分格式的视频及音频多媒体文件,如光盘,设备和网络流媒体。我们确保你的多媒体文件在原分辨率下播放,同时通过视频播放器:Video Player编码格式的优化使文件播放速度和效果达到最佳效果。视频播放器:Video Player主要特点* 视频播放器:Video Player大多数播放本地视频和... &lt;uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /&gt; &lt;uses-permission android:name="android.permission.WRITE_EX... 5、看本地视频随着新技术的快速发展,网络机顶盒的分辨率已经达到了4k级别,不仅可以播放1080p全高清影片,也能观看3d原盘,以及4k超高清影片,但需要连接u盘或者其他移动存储设备。随着人们对影视娱乐高清晰,高音效的追求,以及现代家庭对大屏投影的需求,电视盒子显然还是最经济实用的视频源,同时,大量的高清爱好者还是热衷于播放本地片源,这个需求要求电视盒子有强大的播放能力,而且盒子cpu任然在不断的升... http://www.w2bc.com/Article/80927 本节带来的是Android多媒体中的——MediaPlayer,我们可以通过这个API来播放音频和视频 该类是Androd多媒体框架中的一个重要组件,通过该类,我们可以以最小的步骤来获取,解码 和播放音视频。它支持三种不同的媒体来源: 内部的URI,比如你可以通过ContentResolver来获取 安卓有很多第三方的播放器,比如最强大的ijkPlayer和最全能的Vitamio播放器,如果是手机APP开发,可以直接拿过来用。但是TV APP开发不行,因为电视盒子的高定制性,兼容性很差,比手机上差很多,这个是我在20+盒子上测试的结果。这中间各种调试,各种参数修改,按下葫芦浮起瓢,经历了无数的噩梦。 最后,不得已,只好抱着试一试的态度,实现原生播放器。结果在所有盒子上,完美适配,简直了。后... 今天,简单讲讲android如何调用手机自带的播放器。昨天,从服务器下载一个AVI的视频,下载后需要进行播放,所以想调用系统自带的播放器。但是由于很少用到,所以自己当时不知道怎么写,于是在网上查找资料,最终是解决了问题。这里记录一下。一.网上的常见调用播放器代码(存在问题)String url = "http://192.168.0.1/1.mp4" Intent intent = new Int... MediaPlayer是Android自带的一个多媒体播放类,可以播放音视频流或者本地音视频文件。MediaPlayer方法的调用需要在一定的状态下,下图是一个MediaPlayer对象被支持的播放控制操作驱动的声明周期和状态。其中,椭圆代表MediaPlayer可能驻留的状态,弧线表示驱动MediaPlayer在各个状态之间迁移的播放控制操作。这里有两种类型的弧线。由单箭头开始的弧线... Android网络编程知识是Android开发过程中必不可少的内容,在网络开发的过程中,我们通常会用到像Volley、OkHttp、Retrofit这些高度封装好的框架,这使得我们的开发很便利但也屏蔽了相关的技术细节。而作为想要进一步的开发者来说,我们不但要会用,有时候更要理解其实现的原理,理解了后更能促进我们更好的使用这些框架。 OSI七层网络模型 国际标准化组织(ISO)在 197...