Android基础课程:原生视频播放器(播放网络资源)

news/2024/11/25 4:44:06/

在之前的博客中,我分析过本地音乐播放器的逻辑和写法,需要使用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"><SurfaceViewandroid: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;@Overrideprotected 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() {@Overridepublic void surfaceCreated(SurfaceHolder holder) {openVideo();}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}@Overridepublic 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() {@Overridepublic void onPrepared(MediaPlayer mp) {mPlayer.start();   //开始播放}});}@Overrideprotected 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"><ListViewandroid: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"><SurfaceViewandroid:id="@+id/id_item_surface"android:layout_width="match_parent"android:layout_height="220dp" /><ImageViewandroid:id="@+id/id_item_thumb"android:layout_width="match_parent"android:layout_height="220dp"android:scaleType="centerCrop"android:src="@mipmap/ic_launcher"/><TextViewandroid: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() {@Overridepublic void onPrepared(MediaPlayer mp) {mp.start();}});mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {mCurrentPosition = -1;notifyDataSetChanged();   //当前视频播放结束了,原来需要播放的不播放了,就通知adapter更新}});}@Overridepublic int getCount() {return mDatas.size();}@Overridepublic Object getItem(int position) {return mDatas.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic 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);//怎么传递当前的位置呢?可以给每个图片设置tagviewHolder.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;}@Overridepublic 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(){@Overridepublic 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();   // 提示适配器更新}}}}};@Overrideprotected 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() {@Overridepublic 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"/>

效果图如下:

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

点击下载相关编码


http://www.ppmy.cn/news/171719.html

相关文章

基于QT的网络音乐播放器(四)

关于歌词的显示&#xff0c;其实我的主要思想就是解析歌词部分的字符串。歌词显示分为两部分&#xff0c;一部分是播放器右侧的歌词显示以及下面的桌面歌词的显示。其中桌面歌词让我很难受&#xff0c;想了很久&#xff0c;后面看到一个大佬的一篇文章后才有了思路。 先看效果…

网络控制播放器(局域网内通过TCP和UDP控制视频播放器)

分享一个视频播放器&#xff0c;这个播放器支持能过网络TCP和UDP协议控制&#xff0c;同时还支持RS232和485串口控制。 可以通过指令控制播放器播放指定的视频&#xff0c;音量控制&#xff0c;图片浏览等。 播放器支持与所有的中控软件对接。 控制指令&#xff1a; 进入视频…

基于android的网络音乐播放器-网络音乐的搜索和展示(五)

作为android初学者&#xff0c;最近把疯狂android讲义和疯狂Java讲义看了一遍&#xff0c;看到书中介绍的知识点非常多&#xff0c;很难全部记住&#xff0c;为了更好的掌握基础知识点&#xff0c;我将开发一个网络音乐播放器-EasyMusic来巩固下&#xff0c;也当作是练练手。感…

易语言在线播放器源码php,易语言视频播放器源代码

用简单的语言编写视频播放器的源代码 源代码包含以下功能: *调用Thunder APlayer SDK进行视频播放*自动检测是否下载Thunderbolt解码库,如果不存在,则自动下载并解压缩(新手可以学习)新手可以学习)*支持快捷键可快进和快退以及将音量最多增加或减小至1000 *自动截取本地视频缩…

基于QT的网络音乐播放器(一)

自学Qt已经有一段时间了&#xff0c;但是始终感觉自己还是很弱&#xff08;其实并不是感觉自己很弱&#xff0c;是自己本来就很弱&#xff0c;哈哈&#xff09;。自己也照着书上敲了几个例子&#xff0c;但觉得还是要写点东西才能真正运用起来。所以&#xff0c;前段时间就写了…

android带投屏播放器,手机投屏播放器软件下载-投屏播放器 安卓版v2.4.6-PC6安卓网...

手机投屏播放器软件是一款非常不错的投屏神器&#xff0c;使用投屏播放器app用户可以将手机上的视频投放在光滑的墙面上&#xff0c;投屏播放器app也能投放到Tv上&#xff0c;设置方法非常简单&#xff0c;喜欢的玩家不要错过&#xff0c;欢迎下载&#xff01; 软件介绍 手机投…

dlna android播放器,dlna音乐播放器app

dlna音乐播放器app是一款基于DLNA协议,能把手机音乐在音响,电脑,电视机等设备上进行播放的应用软件,这款工具适用于当前市面上所有的安卓手机,只需要按照操作步骤就能完美使用了,让科技改善生活,个性引领潮流。 应用简介: 基于DLNA协议,通过app把手机的音频与网络音频…

Qt实现网络播放器

经常有人询问一些关于网络传输、制作在线试听及下载 音乐、构造及解析数据等的一些问题&#xff0c;今天就在这里一并讲解。 网络操作&#xff1a; 主要涉及&#xff1a;QNetworkAccessManager、QNetworkRequest、QNetworkReply这三个类。 参考&#xff1a; Qt实现网络数据传输…