【Android 多媒体开发】 MediaPlayer 网络视频播放器(二)

简介: 【Android 多媒体开发】 MediaPlayer 网络视频播放器(二)

4. MediaPlayer 播放





(1) 设置音量 和 播放载体



设置音量 :



mediaPlayer.setAudioStreamType(2);    /* 设置播放音量 */


设置播放载体 : 调用 setDisplay() 方法, 传入 SurfaceHolder 对象;


mediaPlayer.setDisplay(surface_holder);  /* 设置播放载体 */


(2) 设置各种监听器



设置错误监听器 : 如果出现错误, 会回调该监听器中的方法, 并提供错误码;



/* 设置 MediaPlayer 错误监听器, 如果出现错误就会回调该方法打印错误代码 */
    mediaPlayer.setOnErrorListener(new OnErrorListener() {
    @Override
    public boolean onError(MediaPlayer arg0, int what, int extra) {
      System.out.println("MediaPlayer 出现错误 what : " + what + " , extra : " + extra);
      return false;
    }
    });


设置缓冲进度监听器 : 缓冲有进展后, 回调该监听器中的方法, 传入缓冲的数据百分比;


/* 设置缓冲进度更新监听器 */
    mediaPlayer.setOnBufferingUpdateListener(new OnBufferingUpdateListener() {
    @Override
    public void onBufferingUpdate(MediaPlayer arg0, int percent) {
      /* 打印缓冲的百分比, 如果缓冲 */
      System.out.println("缓冲了的百分比 : " + percent + " %");
    }
    });


设置播放完毕监听器 : 播放完毕后会回调该监听器中的方法;


/* 设置播放完毕监听器 */
    mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
    @Override
    public void onCompletion(MediaPlayer arg0) {
      System.out.println("播放完毕了");
      status.setText("播放完毕");
    }
    });


设置准备完毕回调监听器 : 准备完毕后会回调该方法;


/* 设置准备完毕监听器 */
    mediaPlayer.setOnPreparedListener(new OnPreparedListener() {
    @Override
    public void onPrepared(MediaPlayer arg0) {
      System.out.println("准备完毕");
      /* 设置播放状态 */
      status.setText("播放中");
    }
    });





二. 代码示例



1. 布局文件代码




<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="20dp"
    tools:context="cn.org.octopus.videodemo.HomeActivity"
    tools:ignore="MergeRootFrame" 
    android:orientation="vertical">
    <AutoCompleteTextView 
        android:id="@+id/url"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:singleLine="true"
        android:ellipsize="end"
        android:completionThreshold="1"
        android:text="http://daily3gp.com/vids/747.3gp"
        android:completionHint="选择下载的视频地址"/>
    <SurfaceView 
        android:id="@+id/surface_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="3"/>
    <TextView 
        android:id="@+id/status"
        android:layout_width="match_parent"
        android:gravity="center"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:textSize="20dp"
        android:text="状态"/>
    <LinearLayout 
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="center_vertical"
        android:orientation="horizontal">
        <Button 
            android:id="@+id/play"
            android:layout_width="0dp" 
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClick"
            android:text="播放"/>
        <Button 
            android:id="@+id/pause"
            android:layout_width="0dp" 
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClick"
            android:text="暂停"/>
        <Button 
            android:id="@+id/reset"
            android:layout_width="0dp" 
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClick"
            android:text="重放"/>
        <Button 
            android:id="@+id/stop"
            android:layout_width="0dp" 
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="onClick"
            android:text="停止"/>
    </LinearLayout>
</LinearLayout>




2. AndroidManifest 代码




<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.org.octopus.videodemo"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk
        android:minSdkVersion="17"
        android:targetSdkVersion="19" />
    <uses-permission android:name="android.permission.INTERNET"/>
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="cn.org.octopus.videodemo.HomeActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>



3. Activity 代码




package cn.org.octopus.videodemo;
import java.io.IOException;
import android.R.anim;
import android.app.Activity;
import android.content.DialogInterface.OnClickListener;
import android.graphics.PixelFormat;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnBufferingUpdateListener;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.TextView;
/** 
 * 
 * 
 * SurfaceHolder  
 *    1. 简介 : 是 Surface 的控制器, 用于控制 SurfaceView 绘图, 处理画布上的动画, 渲染效果, 大小等;
 *    2. 常用方法 : 
 *    -- abstract void addCallback(SurfaceHolder.Callback callback) : 添加一个 SurfaceHolder.Callback 接口对象, 监听 Surface 的开始结束绘制大小改变事件;
 *    -- abstract Canvas lockCanvas() : 锁定画布, 可以获得 Canvas 对象, 之后就可以在 Canvas 上绘图了;
 * 
 * SurfaceHolder.Callback接口 : 
 *    1. Surface 绘图边界 : 所有的绘图工作都在 Surface 创建之后才能进行, 在 Surface 销毁之前结束;
 *    2. Callback 接口对应的 Surface 边界 : surfaceCreated() 方法在开始绘制时回调, surfaceDestroyed() 在 Surface 销毁前回调;
 *    3. 该接口中的方法 : 
 *    -- surfaceChanged() : 在 Surface 大小改变时回调;
 *    -- surfaceCreated() : 在 Surface 创建时回调;
 *    -- surfaceDestroyed() : 在 Surface 销毁时回调;
 * 
 * @author octopus 
 *
 */
public class HomeActivity extends Activity implements SurfaceHolder.Callback {
  private AutoCompleteTextView url;       /* 地址输入框, 带自动提示功能 */
  private SurfaceView surface_view;       /* 播放视频载体 */
  private TextView status;          /* 显示播放状态 */
  private Button play;          /* 播放按钮 */
  private Button pause;          /* 咱提供按钮 */
  private Button reset;          /* 重放按钮 */
  private Button stop;          /* 停止按钮 */
  private MediaPlayer mediaPlayer;        /* 播放器 */
  private SurfaceHolder surface_holder;       /* Surface 控制器 */
  private boolean isStartPlaying;  /* 是否开始了播放 */
  @Override
  protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_home);
  initViews();
  initData();
  }
  /**
  * 初始化成员变量中的组件变量
  */
  private void initViews() {
  /* 通过 findViewById 获取相关方法 */
  url = (AutoCompleteTextView) findViewById(R.id.url);
  surface_view = (SurfaceView) findViewById(R.id.surface_view);
  status = (TextView) findViewById(R.id.status);
  play = (Button) findViewById(R.id.play);
  pause = (Button) findViewById(R.id.pause);
  reset = (Button) findViewById(R.id.reset);
  stop = (Button) findViewById(R.id.stop);
  /* 设置一个列表适配器 */
  String[] urls = {
    "http://daily3gp.com/vids/747.3gp",
    "http://daily3gp.com/vids/Funny%20women%20cannot%20understand.3gp",
    "http://k.youku.com/player/getFlvPath/sid/9409280845322127f6c57_00/st/flv/fileid/0300020100540024BC9E5C08BD8A98D8200E2B-7950-B9A5-8669-DC283BDCC077?K=3a58dc2cdcc532df261dddec&ctype=12&ev=1&oip=1931322792&token=5696&ep=eyaUE0uFVsYE4CDdij8bYHrkJ3IIXP4J9h%2BFg9JjALshTOi%2FmzqjtJTFS4xCHottelMPGJ%2F5qdDnH0JmYfdKrGgQrUfZPPro%2BPbq5dkgxpgDFG1FAc3Qs1SbRTn3",
    "http://k.youku.com/player/getFlvPath/sid/9409280845322127f6c57_00/st/flv/fileid/030002040053FFB59E433100422C39BAFA46CC-4DED-E928-87B8-91706CDB5FF2?K=645d8478a3aa59052411eb8a&ctype=12&ev=1&oip=1931322792&token=5696&ep=eyaUE0uFVsYE4CDdij8bYHrkJ3IIXP4J9h%2BFg9JmALshS57J6zvYspmzTf5CFv0bcFEFGZmA3aHjbDNnYfQ33BwQqkeqMfro%2BYLr5aRSw5AGFW1Ed7uhtlSbRTn3"
  };
  /* 创建数组适配器 */
  ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, 
    android.R.layout.simple_dropdown_item_1line, urls);
  /* 将适配器设置给 AutoCompleteTextView 组件对象 */
  url.setAdapter(adapter);
  /* 使窗口支持透明度, 把当前 Activity 窗口设置成透明, 设置了该选项就可以使用 setAlpha 等函数设置窗口透明度 */
  getWindow().setFormat(PixelFormat.TRANSPARENT);
  }
  /**
  * 初始化相关数据变量
  */
  private void initData() {
  /* 获取并设置 SurfaceHolder 对象 */
  surface_holder = surface_view.getHolder();      /* 根据 SurfaceView 组件, 获取 SurfaceHolder 对象 */
  surface_holder.addCallback(this);         /* 为 SurfaceHolder 设置回调函数, 即 SurfaceHolder.Callback 子类对象 */
  surface_holder.setFixedSize(160, 128);        /* 设置视频大小比例 */
  surface_holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);/* 设置视频类型 */
  }
  /**
  * 设置点击事件
  * @param view
  */
  public void onClick(View view) {
  int id = view.getId();
  switch (id) {
  case R.id.play:
    /* 播放视频直接从 AutoCompleteTextView 中获取字符串, 播放该 url 代表的网络视频 */
    playVideo(url.getText().toString());
    break;
  case R.id.pause:
    if(mediaPlayer != null){
    mediaPlayer.pause();
    status.setText("暂停");
    }
    break;
  case R.id.reset:
    if(mediaPlayer != null){
    mediaPlayer.seekTo(0);
    mediaPlayer.start();
    status.setText("播放中");
    }
    break;
  case R.id.stop:
    if(mediaPlayer != null){
    mediaPlayer.stop();
    mediaPlayer.release();
    isStartPlaying = false;
    status.setText("停止");
    }
    break;
  default:
    break;
  }
  }
  /**
  * 播放网络视频
  * a. 创建并配置 MediaPlayer 对象 (音量, SurfaceHolder)
  * b. 为 MediaPlayer 设置错误监听器, 缓冲进度监听器, 播放完毕监听器, 准备完毕监听器
  * c. 未 MediaPlayer 设置数据源
  * d. 调用 prepare() 进入 Prapared 状态
  * e. 调用 start() 进入 Started 状态
  * 
  * @param dataSource 播放视频的网络地址
  */
  private void playVideo(final String dataSource) {
  /* 点击播放有两种情况 
   * a. 第一次点击 : 需要初始化 MediaPlayer 对象, 设置监听器
   * b. 第二次点击 : 只需要 调用 mediaPlayer 的 start() 方法
   * 两种情况通过 isStartPlaying 点击时间判断 */
  if(isStartPlaying){        /* 如果已经开始了播放, 就直接开始播放 */
    mediaPlayer.start();
  }else{            /* 如果是第一次开始播放, 需要初始化 MediaPlayer 设置监听器等操作 */
    mediaPlayer = new MediaPlayer();    /* 创建 MediaPlayer 对象 */
    mediaPlayer.setAudioStreamType(2);    /* 设置播放音量 */
    mediaPlayer.setDisplay(surface_holder);  /* 设置播放载体 */ 
    /* 设置 MediaPlayer 错误监听器, 如果出现错误就会回调该方法打印错误代码 */
    mediaPlayer.setOnErrorListener(new OnErrorListener() {
    @Override
    public boolean onError(MediaPlayer arg0, int what, int extra) {
      System.out.println("MediaPlayer 出现错误 what : " + what + " , extra : " + extra);
      return false;
    }
    });
    /* 设置缓冲进度更新监听器 */
    mediaPlayer.setOnBufferingUpdateListener(new OnBufferingUpdateListener() {
    @Override
    public void onBufferingUpdate(MediaPlayer arg0, int percent) {
      /* 打印缓冲的百分比, 如果缓冲 */
      System.out.println("缓冲了的百分比 : " + percent + " %");
    }
    });
    /* 设置播放完毕监听器 */
    mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
    @Override
    public void onCompletion(MediaPlayer arg0) {
      System.out.println("播放完毕了");
      status.setText("播放完毕");
    }
    });
    /* 设置准备完毕监听器 */
    mediaPlayer.setOnPreparedListener(new OnPreparedListener() {
    @Override
    public void onPrepared(MediaPlayer arg0) {
      System.out.println("准备完毕");
      /* 设置播放状态 */
      status.setText("播放中");
    }
    });
    new Thread(){
    public void run() {
      try {
      System.out.println("设置数据源");
      mediaPlayer.setDataSource(dataSource);
      mediaPlayer.prepare();
      /* 打印播放视频的时长 */
      System.out.println("视频播放长度 : " + mediaPlayer.getDuration());
      mediaPlayer.start();
      } catch (IllegalStateException e) {
      e.printStackTrace();
      } catch (IOException e) {
      e.printStackTrace();
      }
    };
    }.start();
    /* 设置 MediaPlayer 开始播放标识为 true */
    isStartPlaying = true;
    /* 设置播放状态 */
    status.setText("正在缓冲");
  }
  }
  /**
  * 在 Surface 大小发生改变的时候回调
  * 实现的 SurfaceHolder.Callback 接口方法
  */
  @Override
  public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
  System.out.println("SurfaceHolder.Callback.surfaceChanged : Surface 大小发生改变");
  }
  /**
  * 在 Surface 创建的时候回调, 一般在该方法中开始绘图
  * 实现的 SurfaceHolder.Callback 接口方法
  */
  @Override
  public void surfaceCreated(SurfaceHolder arg0) {
  System.out.println("SurfaceHolder.Callback.surfaceCreated : Surface 开始创建");
  }
  /**
  * 在 Surface 销毁之前回调, 在该方法中停止渲染线程, 释放相关资源
  * 实现的 SurfaceHolder.Callback 接口方法
  */
  @Override
  public void surfaceDestroyed(SurfaceHolder arg0) {
  System.out.println("SurfaceHolder.Callback.surfaceDestroyed : Surface 销毁");
  }
  @Override
  protected void onDestroy() {
  if(mediaPlayer != null)
    mediaPlayer.release();
  super.onDestroy();
  }
}



4. 运行示例


image.png





日志信息 :


octopus@octopus:~/develop/adt-bundle-linux/sdk/tools$ adb logcat -s System.out
--------- beginning of /dev/log/main
--------- beginning of /dev/log/system
I/System.out(21129): 设置数据源
I/System.out(21129): 视频播放长度 : 31200
I/System.out(21129): 准备完毕
I/System.out(21129): 缓冲了的百分比 : 0 %
I/System.out(21129): 缓冲了的百分比 : 37 %
I/System.out(21129): 缓冲了的百分比 : 37 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 缓冲了的百分比 : 100 %
I/System.out(21129): 播放完毕了
I/System.out(21129): 缓冲了的百分比 : 100 %
目录
相关文章
|
5天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
24 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
28天前
|
Java Android开发
Android 开发获取通知栏权限时会出现两个应用图标
Android 开发获取通知栏权限时会出现两个应用图标
14 0
|
1月前
|
机器学习/深度学习 自然语言处理 数据处理
大模型开发:描述长短期记忆网络(LSTM)和它们在序列数据上的应用。
LSTM,一种RNN变体,设计用于解决RNN处理长期依赖的难题。其核心在于门控机制(输入、遗忘、输出门)和长期记忆单元(细胞状态),能有效捕捉序列数据的长期依赖,广泛应用于语言模型、机器翻译等领域。然而,LSTM也存在计算复杂度高、解释性差和数据依赖性强等问题,需要通过优化和增强策略来改进。
|
1月前
|
机器学习/深度学习
大模型开发:解释卷积神经网络(CNN)是如何在图像识别任务中工作的。
**CNN图像识别摘要:** CNN通过卷积层提取图像局部特征,池化层减小尺寸并保持关键信息,全连接层整合特征,最后用Softmax等分类器进行识别。自动学习与空间处理能力使其在图像识别中表现出色。
24 2
|
1月前
|
网络协议 C++
C++ Qt开发:QTcpSocket网络通信组件
`QTcpSocket`和`QTcpServer`是Qt中用于实现基于TCP(Transmission Control Protocol)通信的两个关键类。TCP是一种面向连接的协议,它提供可靠的、双向的、面向字节流的通信。这两个类允许Qt应用程序在网络上建立客户端和服务器之间的连接。Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用`QTcpSocket`组件实现基于TCP的网络通信功能。
38 8
C++ Qt开发:QTcpSocket网络通信组件
|
2天前
|
数据库 Android开发 开发者
安卓应用开发:构建高效用户界面的策略
【4月更文挑战第24天】 在竞争激烈的移动应用市场中,一个流畅且响应迅速的用户界面(UI)是吸引和保留用户的关键。针对安卓平台,开发者面临着多样化的设备和系统版本,这增加了构建高效UI的复杂性。本文将深入分析安卓平台上构建高效用户界面的最佳实践,包括布局优化、资源管理和绘制性能的考量,旨在为开发者提供实用的技术指南,帮助他们创建更流畅的用户体验。
|
19天前
|
XML 开发工具 Android开发
构建高效的安卓应用:使用Jetpack Compose优化UI开发
【4月更文挑战第7天】 随着Android开发不断进化,开发者面临着提高应用性能与简化UI构建流程的双重挑战。本文将探讨如何使用Jetpack Compose这一现代UI工具包来优化安卓应用的开发流程,并提升用户界面的流畅性与一致性。通过介绍Jetpack Compose的核心概念、与传统方法的区别以及实际集成步骤,我们旨在提供一种高效且可靠的解决方案,以帮助开发者构建响应迅速且用户体验优良的安卓应用。
|
22天前
|
监控 算法 Android开发
安卓应用开发:打造高效启动流程
【4月更文挑战第5天】 在移动应用的世界中,用户的第一印象至关重要。特别是对于安卓应用而言,启动时间是用户体验的关键指标之一。本文将深入探讨如何优化安卓应用的启动流程,从而减少启动时间,提升用户满意度。我们将从分析应用启动流程的各个阶段入手,提出一系列实用的技术策略,包括代码层面的优化、资源加载的管理以及异步初始化等,帮助开发者构建快速响应的安卓应用。
|
22天前
|
Java Android开发
Android开发之使用OpenGL实现翻书动画
本文讲述了如何使用OpenGL实现更平滑、逼真的电子书翻页动画,以解决传统贝塞尔曲线方法存在的卡顿和阴影问题。作者分享了一个改造后的外国代码示例,提供了从前往后和从后往前的翻页效果动图。文章附带了`GlTurnActivity`的Java代码片段,展示如何加载和显示书籍图片。完整工程代码可在作者的GitHub找到:https://github.com/aqi00/note/tree/master/ExmOpenGL。
23 1
Android开发之使用OpenGL实现翻书动画
|
22天前
|
Android开发 开发者
Android开发之OpenGL的画笔工具GL10
这篇文章简述了OpenGL通过GL10进行三维图形绘制,强调颜色取值范围为0.0到1.0,背景和画笔颜色设置方法;介绍了三维坐标系及与之相关的旋转、平移和缩放操作;最后探讨了坐标矩阵变换,包括设置绘图区域、调整镜头参数和改变观测方位。示例代码展示了如何使用这些方法创建简单的三维立方体。
18 1
Android开发之OpenGL的画笔工具GL10