西甲2015赛程表
最新安卓入门教程
扫码关注
获取更多技术干货

image

作者论坛ID:我是农民的孩子    

https://github.com/huangxunlei

版权声明:本章节为安卓巴士博主原创文章,未经授权不得以?#25105;廡问?#36716;载发布/发表,违者将依法追究责任


8.1 多?#25945;?#27010;述

一 什么是多?#25945;?/h2>

多?#25945;?#30340;英文单词是Multimedia,它由media和multi两部分组成。一般理解为多种?#25945;?#30340;综合

多?#25945;?#26159;计算机和视频技术的结合,实际上它是两个?#25945;澹?#22768;音和图像,或者用现在的术语:音响和电视。

多?#25945;澹∕ultimedia),在计算机系?#25345;校?#32452;合两种或两种以上?#25945;?#30340;一种人机交互式信息交流和传播?#25945;?/p>

使用的?#25945;?#21253;括文字、图片、照片、声音(包含音乐、语音旁白、特殊音效)、动画和影片,以及程式所提供的互动功能

多?#25945;?#26159;超?#25945;?#31995;?#25345;?#30340;一个子集,而超?#25945;?#31995;统是使用超链接构成的全球信息系统

全球信息系统是因特网上使用 TCP/IP 协议和 UDP/IP 协议

二、本章提纲

在过去的几十年里,手机的用途和功能比较单一,只能用于拨打电话和发送短信。随着时代的进步,科技的不断发展,智能机的出现改变了人们的生活方式。如今的时代,手机在我们的生活中成为了不可缺少的一部分,每天把自己的生活奇遇发到朋友圈,有时候拍成短视频,一起与朋友分享。

而这些都来自手机强大的多?#25945;?#30340;支撑,android在这些方面也提供了相应的API,从而使我们的应用更加丰富多彩,本章主要是主要介绍android中一些常用的多媒功能的使用技术,既要从一?#24405;?#20010;方面学习:

  • 手机上运行你的应用程序
  • 通知
  • 音频
  • 视频
  • 相册

第一节 如何在手机运行你的应用程序

首先智能手机一部、USB连接线,?#27604;?#20102;这里的usb线是能够传输数据的那种,只能充电的多数都没见过了吧、电脑一台。

用数据线把手机连接到电脑上,找到你的爱机 设置-->(更多设置)->关于手机->软件版本号, 连续点击版本号,就会激活开发者模式,?#27604;?#20102;这些都是比较常见的手机的打开方式,但是对于小米的手机就不同了,

image

然后返回,找到开发者模式点击进入,打开usb调试,手机上提示是否允许电脑连接,手机点击允许,如果你用的是window系统的电脑,电脑会自动去找下载相应的驱动,如果下载不成功,你?#37096;?#20197;借助360助手,应用宝等工具进行安装。?#27604;?#22914;果你的电脑是Linux系统,驱动什么的都可以免了,因为android系统本身就是基于linux系统的。

运行程序的时候直接选择手机安装即可,在手机上运行。

可能觉得奇怪为什么前面几章都没有提及到在真机上运行突然在这里写出来了呢, 如果有这样的疑问那么?#24471;?#20320;前面的章节你是认真的看了的,为什么要在这里给同学们介绍真机的运行呢,

首先模拟器上面很多东西都是虚拟的,如果用的是android开发工具中自带的模拟器,是没有安装语音等驱动的。本章涉及到的是多?#25945;?#30456;关的功能的介绍,那么就要通过接口去操作手机的硬件,而在模拟器上面是没有这些硬件的,所以建议读者本章的程序都在真机运行,?#27604;?#20102;以后的开发中涉及相关语音等功能,?#27493;?#35758;在真机上调试开发。

8.2 通知的使用

一、什么是通知(Notification)

通知是一个可以在应用程序正常的用户界面之外显示给用户的消息

通知发出时,它首先出现在状态栏的通知区域中,用户打开通知抽屉可查看通知详情。通知区域和通知抽屉都是用户可以随时查看的系统控制区域。

二、通知栏的改动

在每个android版本的更新中通知栏都有或多或少的改动,

在界面风格上面,在通知栏androidN 通知栏变动大:

? 通知消息快捷回复

安卓7.0加入了全新的API,支持第三方应用通知的快捷操作和回复,例如来电会以横幅方式在屏幕顶部出现,提供接听/挂断两个按钮;信息/社交类应用通知,还可以直接打开键盘,在输入栏里进行快捷回复。

? 通知消息归拢

安卓7.0会将同一应用的多条通知提示消息归拢为一项,点击该项即可展开此前的全部通知,允许用户对每个通知执行单独操作。

? 通知时间的显示

安卓7.0通知显示的时间默认是跟随在标题的后面,而不是以往的通知栏的右边

三、创建通知的变化

随着Android系统不断升级,Notification的创建方?#25581;?#38543;之变化,主要变化如下:

Android 3.0之前

Android 3.0 (API level 11)之前,使用new Notification()方式创建通知:

  val mNotifyMgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
  val contentIntent = PendingIntent.getActivity(
      this, 0,  Intent(this, ResultActivity.class), 0);
val notification =  Notification(icon, tickerText, when);
notification.setLatestEventInfo(this, title, content, contentIntent);
mNotifyMgr.notify(NOTIFICATIONS_ID, notification)

Android 3.0 (API level 11)及更高版本

  val mNotifyMgr = 
      (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
val contentIntent = PendingIntent.getActivity(
      this, 0, new Intent(this, ResultActivity.class), 0);

val notification = new Notification.Builder(this)
            .setSmallIcon(R.drawable.notification_icon)
            .setContentTitle("我的通知")
            .setContentText("通知的内容。。。")
            .setContentIntent(contentIntent)
            .build();// getNotification()

mNotifyMgr.notify(NOTIFICATIONS_ID, notification);

这里需要注意: "build()" 是Androdi 4.1(API level 16)加入的,用以替代 "getNotification()"。API level 16开?#35745;?#29992;"getNotification()"

兼容Android 3.0之前的版本

为了兼容API level 11之前的版本,v4 Support Library中提供了 NotificationCompat.Builder()这个替代方法。它与Notification.Builder()类似,二者没有太大区别。

val btm = BitmapFactory.decodeResource(resources,
                R.mipmap.ic_smail)
        val builder = NotificationCompat.Builder(this)
        val intent = Intent(this, MainActivity::class.java)  //需要跳转指定的页面
        val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
        builder.setContentIntent(pendingIntent)
        builder.setSmallIcon(R.mipmap.ic_smail)// 设置图标
        builder.setContentTitle("外卖订单的通知")// 设置通知的标题
        builder.setContentText("通知的内容")// 设置通知的内容
        builder.setWhen(System.currentTimeMillis())// 设置通知来到的时间
        builder.setAutoCancel(true) //自己维护通知的消失
        builder.setTicker("new message")// 第一次提示消失的时候显示在通知栏上的
        builder.setOngoing(true)
        builder.setLargeIcon(btm)
        builder.setNumber(20)

        val notification = builder.build()
        notification.flags = Notification.FLAG_NO_CLEAR  //只有全部清除时,Notification才会清除
        notificationManager!!.notify(0, notification)
    }

    private fun myNotify() {

        //实例化一个NotificationCompat.Builder对象
        val builder = NotificationCompat.Builder(this)
                .setContentText("你有新的外卖订单")
                .setContentTitle("外卖通知")
                .setSmallIcon(R.mipmap.ic_smail)

        //定义并设置一个通知动作(Action)
        val resultIntent = Intent(this, ResultActivity::class.java)
        val pi = PendingIntent.getActivity(this, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT)
        builder.setContentIntent(pi)
        //生成Notification对象
        val notification = builder.build()

        //使用NotificationManager发送通知
        val notifiManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notifiManager.notify(0, notification)

四、通知基本用法

通知的必要属性

一个通知必须包含以下三项属性:

  • 小图标,对应 setSmallIcon()
  • 通知标题,对应 setContentTitle()
  • 详细信息,对应 setContentText()

其他属性均为可选项,更多属性方法请参考NotificationCompat.Builder。

尽管其他都是可选的,但一般都会为通知添加至少一个动作(Action),这个动作可以是跳转到==Activity==、启动一个==Service==或发送一个==Broadcast==?#21462;?通过以?#36335;?#24335;为通知添加动作:

  • 使用PendingIntent
  • 通过大视图通知的 Action Button //仅支持Android 4.1 (API level 16)及更高版本,稍后会介绍

    创建通知

  1. 实例化一个NotificationCompat.Builder对象
  2. 定义并设置一个通知动作(Action)
  3. 生成Notification对象
  4. 使用NotificationManager发送通知

代码实例


        //实例化一个NotificationCompat.Builder对象
        val builder = NotificationCompat.Builder(this)
                .setContentText("你有新的外卖订单")
                .setContentTitle("外卖通知")
                .setSmallIcon(R.mipmap.ic_smail)

        //定义并设置一个通知动作(Action)
        val resultIntent = Intent(this, ResultActivity::class.java)
        val pi = PendingIntent.getActivity(this, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT)
        builder.setContentIntent(pi)
        //生成Notification对象
        val notification = builder.build()

        //使用NotificationManager发送通知
        val notifiManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notifiManager.notify(0, notification)

更新通知

更新通知很简单,只需再次发送相同ID的通知即可,如果之前的通知依然存在则会更新通知属性,如果之?#24052;?#30693;不存在则重新创建。

示例代码:

 val mNotifyMgr = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        // Sets an ID for the notification, so it can be updated
        val notifyID = 1
        val mNotifyBuilder = NotificationCompat.Builder(this)
                .setContentTitle("New Message")
                .setContentText("You've received new messages.")
                .setSmallIcon(R.mipmap.ic_launcher)

        var numMessages = 0

        mNotifyBuilder.setContentText("new content text")
                .setNumber(++numMessages)
        mNotifyMgr.notify(notifyID, mNotifyBuilder.build())

取消通知

取消通知有如下4种方式:

  • 点击通知栏的清除按钮,会清除所有可清除的通知
  • 设置了 setAutoCancel() 或 FLAG_AUTO_CANCEL的通知,点击该通知时会清除它
  • 通过 NotificationManager 调用 cancel() 方法清除指定ID的通知
  • 通过 NotificationManager 调用 cancelAll() 方法清除所有该应用之前发送的通知

五、通知类型

大视图通知

通知有两种视图:普通视图和大视图。

普通视图:

image

大视图:

image

默认情况下为普通视图,可通过NotificationCompat.Builder.setStyle()设?#20040;?#35270;图。 注: 大视图(Big Views)由Android 4.1(API level 16)开始引入,且仅支持Android 4.1及更高版本。

构建大视图通知

以上图为例: 1、构建Action Button的PendingIntent

    val dismissIntent = Intent(this, PingService::class.java)
        dismissIntent.action = CommonConstants.ACTION_DISMISS
        val piDismiss = PendingIntent.getService(
                this, 0, dismissIntent, 0)

        val snoozeIntent = Intent(this, PingService::class.java)
        snoozeIntent.action = CommonConstants.ACTION_SNOOZE
        val piSnooze = PendingIntent.getService(this, 0, snoozeIntent, 0)

2、构建NotificatonCompat.Builder对象

 builder.setContentIntent(pendingIntent)
        builder.setSmallIcon(R.mipmap.ic_smail)// 设置图标
        builder.setContentTitle("外卖订单的通知")// 设置通知的标题
        builder.setContentText("通知的内容")// 设置通知的内容
        builder.setWhen(System.currentTimeMillis())// 设置通知来到的时间
        builder.setAutoCancel(true) //自己维护通知的消失
        builder.setTicker("new message")// 第一次提示消失的时候显示在通知栏上的
        builder.setOngoing(true)
        builder.setLargeIcon(btm)
        builder.setNumber(20)

        val notification = builder.build()
        notification.flags = Notification.FLAG_NO_CLEAR  //只有全部清除时,Notification才会清除
        notificationManager!!.notify(0, notification)

3、其他步骤与普通视图相同

通知常见属性和常量

1、声音提醒

//默认声音
notification.defaults |= Notification.DEFAULT_SOUND;
//自定义声音
notification.sound = Uri.parse("file:///sdcard0/notification.ogg");

2、震动提醒

//默认振动
notification.defaults |= Notification.DEFAULT_VIBRATE;
//自定义振动
long[] vibrate = {100, 200, 300, 400}; //震动效果
// 表示在100、200、300、400这些时间点交替启动和关?#29031;?#21160; notification.vibrate = vibrate;

3、?#20102;?#25552;醒

//默认?#20102;?notification.defaults |= Notification.DEFAULT_LIGHTS;
//自定义?#20102;?notification.ledARGB = 0xff00ff00; // LED灯的颜色,绿灯
notification.ledOnMS = 300; // LED灯显示的毫秒数,300毫秒
notification.ledOffMS = 1000; // LED灯关闭的毫秒数,1000毫秒
notification.flags |= Notification.FLAG_SHOW_LIGHTS; // 必须加上这个标志

常见的Flags

FLAG_AUTO_CANCEL //当通知被用户点击之后会自动被清除(cancel)
FLAG_INSISTENT  //在用户响应之前会一直重复提醒音
FLAG_ONGOING_EVENT  //表示正在运行的事件
FLAG_NO_CLEAR   //通知栏点击“清除”按钮时,该通知将不会被清除
FLAG_FOREGROUND_SERVICE  表示当前服务是前台服务

更多Notification属性详见Notification。

参考文章

1、Android Notification自定义通知样式你要知道的事

2、Android通知详解

3、Android--通知之Notification

8.3 调用相册和摄像头

一、调用摄像?#26041;?#34892;拍照

大部分多应用程序都可能会使用到调用摄像头拍照的功能,比如说程序里需要上传一张图片 作为用户的头像,这时打开摄像头拍张照是最简单快捷的。 下面就让我们通过一个例子来学 习一下,如何才能在应用程序里调用手机的摄像?#26041;?#34892;拍照。

新建一个 ChoosePicActivity页面?#22363;蠥ppCompatActivity, 然后新建布局文件 activity_choose_pic.xml 中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <Button
        android:id="@+id/btn_opnen_carmer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="打开相机"/>

    <ImageView
        android:id="@+id/img_show_avatar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>

可以看到,布局文件中只有两个控件,一个 Button 和一个 ImageView。Button 是用于打 开摄像?#26041;?#34892;拍照的,而 ImageView 则是用于将拍到的图片显示出来。

然后开始编写调用摄像头的具体逻辑,ChoosePicActivity中的代码,如下所示:

public class ChoosePicActivity extends AppCompatActivity {
 private var mBtnOpenCamer: Button? = null
    private var mBtnOpenAlbum: Button? = null
    private var mImgAvatar: ImageView? = null
    private var imageUri: Uri? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_choose_pic)
        initView()

        mBtnOpenCamer!!.setOnClickListener {
            val outputImage = File(Environment.getExternalStorageDirectory(),
                    "avatar" + ".jpg")
            val intent = Intent()
            try {
                if (outputImage.exists()) {
                    outputImage.delete()
                }
                outputImage.createNewFile()
            } catch (e: IOException) {
                e.printStackTrace()
            }

            //7.0以上的手机进行处理
            if (Build.VERSION.SDK_INT >= 24) {
                imageUri = FileProvider.getUriForFile([email protected], "com.xingkong.unit8_demo", outputImage)//通过FileProvider创建一个content类型的Uri
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            } else {
                imageUri = Uri.fromFile(outputImage)
                Log.e("hxl", imageUri!!.toString())
            }

            intent.action = MediaStore.ACTION_IMAGE_CAPTURE
            intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
            startActivityForResult(intent, TAKE_PHOTO)
        }

        mBtnOpenAlbum!!.setOnClickListener {
            val intent = Intent(Intent.ACTION_PICK)
            intent.type = "image/*"
            startActivityForResult(intent, CROP_PHOTO)
        }
    }

    /**
     * 初始化布局
     */
    private fun initView() {
        mBtnOpenCamer = findViewById(R.id.btn_opnen_carmer) as Button
        mImgAvatar = findViewById(R.id.img_show_avatar) as ImageView
        mBtnOpenAlbum = findViewById(R.id.btn_opnen_album) as Button
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        when (requestCode) {
            TAKE_PHOTO -> if (resultCode == Activity.RESULT_OK) {
                try {
                    mImgAvatar!!.setImageBitmap(BitmapFactory.decodeStream(contentResolver.openInputStream(imageUri!!)))
                } catch (e: FileNotFoundException) {
                    e.printStackTrace()
                }

            }
            CROP_PHOTO -> if (resultCode == Activity.RESULT_OK) {
                try {
                    if (data != null) {
                        val uri = data.data
                        imageUri = uri
                    }
                    val bitmap = BitmapFactory.decodeStream(contentResolver
                            .openInputStream(imageUri!!))
                    mImgAvatar!!.setImageBitmap(bitmap)
                } catch (e: FileNotFoundException) {
                    e.printStackTrace()
                }

            }
        }
    }

    companion object {

        val TAKE_PHOTO = 1//拍照
        val CROP_PHOTO = 2//相册
    }
}

上述代码稍微有点复杂,我们来仔细地分析一下。在 ChoosePicActivity 中要做的第一件事自然是分别获取到Button 和 ImageView 的实例, 并给 Button 注册上点击事件, 然后在 Button 的点击事件里开?#21363;?#29702;调用摄像头的逻辑, 我们重点看下这部分代码。

首先这里创建了一个 File 对象,用于存储摄像头拍下的图片, 这里我们把图片命名为 avatar.jpg , 并将它存放在手机SD卡的根目录下调用 Environment 的 getExternalStorageDirectory() 方法获取到的就是手机 SD 卡的根目录。 然后再调用 Uri 的 fromFile()方法将 File 对象转换成 Uri 对象, 这个 Uri 对象标识着 avatar.jpg 这张图片 的唯一地址。 接着构建出一个 Intent 对象, 并将这个 Intent 的 action 指定为 android.media.action. IMAGE_CAPTURE, 再调用 Intent 的 putExtra()方法指定图片的输出地址, 这里填入刚刚得 到的 Uri 对象, 最后调用 startActivityForResult()来启动活动。 由于我们使用的是一个隐式 Intent, 系统会找出能够响应这个 Intent 的活动去启动, 这样照相机程序就会被打开,拍下的 照片将会输出到 avatar.jpg 中。

注意刚才我们是使用 startActivityForResult()来启动活动的, 因此拍完照后会有结果返回 到 onActivityResult()方法中。如果发现拍照成功, 操作完成之后,程序?#21482;?#22238;调到 onActivityResult()方法中, 这个时候我们就可以调 用 BitmapFactory 的 decodeStream()方法将 avatar.jpg 这张照片解析成 Bitmap 对象, 然后把它设置到 ImageView 中显示出来。

由于这个项目涉及到了向 SD 卡?#34892;?#25968;据的操作, 因此我们还需要在 AndroidManifest.xml中声明权限:

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

同时在代码中我们可以到当手机系统更大于7.0是处理拍照的方法就不同了。

FileProvider 的使用

  1. 在manifest中添加provider
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.xingkong.unit8_demo"
            android:exported="false"
            android:grantUriPermissions="true"        >
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"/>
        </provider>

        //exported:要求必须为false,为true则会报安全异常。
        //grantUriPermissions:true,表示授予 URI 临时访问权限。
  1. ?#35797;?#25991;件下创建相应的xml文件(如上:则创建filepaths.xml)。
    <?xml version="1.0" encoding="utf-8"?>
    <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
     <paths>
         <external-path
             name="camera_photos"
             path=""/>
     </paths>
    </PreferenceScreen>
    代表的根目录: Context.getFilesDir() 代表的根目录: Environment.getExternalStorageDirectory()
代表的根目录: getCacheDir()
  1. FileProvider
    //7.0以上的手机进行处理
             if (Build.VERSION.SDK_INT >= 24) {
                 imageUri = FileProvider.getUriForFile([email protected], "com.xingkong.unit8_demo", outputImage)//通过FileProvider创建一个content类型的Uri
                 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
             } else {
                 imageUri = Uri.fromFile(outputImage)
                 Log.e("hxl", imageUri!!.toString())
             }

2、调取系统相册

val intent = Intent(Intent.ACTION_PICK)
            intent.type = "image/*"
            startActivityForResult(intent, CROP_PHOTO)

8.4 播放多?#25945;?#25991;件---音乐播放器

1.Android实现简单音乐播放器

本节内容

实现一个简单的音乐播放器,要求功能有:

  • 播放、暂停功能;
  • 进度条显示播放进度功能
  • 拖动进度条改变进度功能;
  • 后台播放功能;
  • 停止功能;
  • 退出功能;
  1. MediaPlayer 介绍

常用方法

  1. 下载音乐文件

随便下载一首歌曲保存在手机中.找到该歌曲的路径.

  1. 使用 MediaPlayer

    创建对象初始化:

    播放/暂停:

停止:

  1. Service 的使用

创建 service 类,实现 MediaPlayer 的功能。 注意在 AndroidManifest.xml 文件里注册 Service: 通过 Binder 来保持 Activity 和 Service 的通信(写在 service 类):

在 Activity 中调用 bindService 保持与 Service 的通信(写在 activity 类): Activity 启动时绑定 Service:

bindService 成功后回调 onServiceConnected 函数,通过 IBinder 获取 Service 对 象,实现 Activity 与 Service 的绑定:

停止服务时,必须解除绑定,写入退出按钮中:

此时,在 Activity 的 onCreate 方法中执行上述与 Service 通信的方法后,即可实现 后台播放。点击退出按钮,程序会退出,音乐停止;返回桌面,音乐继续播放。

  1. Handler的使用

Handler 与 UI 是同一线程,这里可以通过 Handler 更新 UI 上的组件状态,Handler 有很多方法,这里使用比较简便的 post 和 postDelayed 方法。

使用 Seekbar 显示播放进度,设置当前值与最大值:

定义 Handler:run 函数中进行更新 seekbar 的进度在类中定义简单?#25484;?#26684;式,用来显 示播放的时间,用 time.format 来格式所需要的数据,用来监听进度条的滑动变化:

  1. 效果图

参考代码

package com.xingkong.unit8_demo

import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.SeekBar
import android.widget.TextView
import java.text.SimpleDateFormat


class MusicActivity : AppCompatActivity() {


    private var mTvBtnState: TextView? = null
    private var mTvTime: TextView? = null
    private var mProgressBar: SeekBar? = null

    private var mBtnState: Button? = null
    private val time = SimpleDateFormat("mm:ss")
    ///storage/emulated/0/kgmusic/download/薛之谦 - 绅士.mp3

    private var musicService: MusicService? = null
    private val sc = object : ServiceConnection {
        override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) {
            musicService = (iBinder as MusicService.MyBinder).service
        }

        override fun onServiceDisconnected(componentName: ComponentName) {
            musicService = null
        }
    }

    var mHandler = Handler()

    var mRunnable: Runnable = object : Runnable {
        override fun run() {
            if (musicService!!.mp.isPlaying()) {
                mTvBtnState!!.text = "播放中..."
                mBtnState!!.text = "暂停"
            } else {
                mTvBtnState!!.text = "暂停中..."
                mBtnState!!.text = "播放"
            }
            mTvTime!!.text = (time.format(musicService!!.mp.getCurrentPosition()) + "/"
                    + time.format(musicService!!.mp.getDuration()))
            mProgressBar!!.progress = musicService!!.mp.getCurrentPosition()

            mProgressBar!!.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
                override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
                    if (fromUser) {
                        musicService!!.mp.seekTo(seekBar.progress)
                    }
                }

                override fun onStartTrackingTouch(seekBar: SeekBar) {

                }

                override fun onStopTrackingTouch(seekBar: SeekBar) {

                }
            })
            mHandler.postDelayed(this, 100)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_music)
        initiView()
        musicService = MusicService()
        bindServiceConnection()
        mProgressBar!!.progress = musicService!!.mp.getDuration()
        mProgressBar!!.max = musicService!!.mp.getDuration()
    }

    private fun initiView() {
        mTvBtnState = findViewById(R.id.tv_btn_state) as TextView
        mTvTime = findViewById(R.id.tv_time) as TextView
        mProgressBar = findViewById(R.id.pb_play) as SeekBar
        mBtnState = findViewById(R.id.btn_state) as Button
    }

    private fun bindServiceConnection() {
        val intent = Intent([email protected], MusicService::class.java)
        startService(intent)
        bindService(intent, sc, BIND_AUTO_CREATE)
    }

    override fun onResume() {
        if (musicService!!.mp.isPlaying()) {
            mTvBtnState!!.text = "播放中..."
            mBtnState!!.text = "暂停"

        } else {
            mTvBtnState!!.text = "暂停中..."
            mBtnState!!.text = "播放"
        }

        mProgressBar!!.progress = musicService!!.mp.getCurrentPosition()
        mProgressBar!!.max = musicService!!.mp.getDuration()
        mHandler.post(mRunnable)
        super.onResume()
        Log.d("hint", "handler post runnable")
    }


    fun playing(view: View) {
        musicService!!.playOrPause()
    }

    fun pause(view: View) {
        musicService!!.stop()
        mProgressBar!!.progress = 0
    }

    fun exit(view: View) {
        mHandler.removeCallbacks(mRunnable)
        unbindService(sc)
        try {
            System.exit(0)
        } catch (e: Exception) {
            e.printStackTrace()
        }

    }

    public override fun onDestroy() {
        unbindService(sc)
        super.onDestroy()
    }


}

Service 类

/**
 * MusicService 2017-10-31
 * Copyright(c)2017 huangxunlei Co.Ltd. All right reserved
 */
package com.xingkong.unit8_demo

import android.app.Service
import android.content.Intent
import android.media.MediaPlayer
import android.os.Binder
import android.os.IBinder

import java.io.IOException

/**
 * class description here
 *
 * @author HuangXunLei
 * @version 1.0.0
 * @since 2017-10-31 00:07
 */
class MusicService : Service() {
    val mBinder: IBinder = MyBinder()
    val mp: MediaPlayer = MediaPlayer()
    private val musicStr = "/storage/sdcard1/MIUI/music/mp3/思美人兮(《思美人》电视剧插曲)_金玟岐_思美人兮.mp3"


    inner class MyBinder : Binder() {
        internal val service: MusicService
            get() = [email protected]
    }

    override fun onBind(intent: Intent): IBinder? {
        return mBinder
    }

    /**
     * 初始化服务
     */
    init {
        try {
            mp!!.setDataSource(musicStr)
            mp!!.prepare()
        } catch (e: Exception) {
            e.printStackTrace()
        }

    }

    /**
     * 暂停或者播放
     */
    fun playOrPause() {
        if (mp!!.isPlaying) {
            mp!!.pause()
        } else {
            mp!!.start()
        }
    }

    /**
     * 停止播放
     */
    fun stop() {
        if (mp != null) {
            mp!!.stop()

            try {
                mp!!.prepare()
                mp!!.seekTo(0)
            } catch (e: IOException) {
                e.printStackTrace()
            }

        }
    }


}

参考文章1

参考文章2

8.5 视屏播放

Android三种播放视频的方式

在Android中,我们有三种方式来实现视频的播放

1、使用其自带的播放器。指定Action为ACTION_VIEW,Data为Uri,Type为其MIME类型。

2、使用VideoView来播放。在布局文件中使用VideoView结合MediaController来实现对其控制。

3、使用MediaPlayer类和SurfaceView来实现,这种方式很灵活。

下面我们分别来实现这三种视屏播放的:

acitivity_video_test.xml的代码为:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_sys"
        android:onClick="systemVideo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="调用系统播放器播放"/>

    <Button

        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="VideoView播放"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="MediaPlay+Surface播放"/>
</Linea

1. 调用其自带的播放器:

使用自带的播放器进行播放视频,就像我们平时打电话一样,直接调用系统的电话即可,不用自己写太多的代码 ,只要通过指定的action即可访问,只要视频地址正确,就会跳转到系统的视屏播放器中进行播放.

如下是调用的方法:


fun systemVideo(view: View?) {
        //Uri.parse(param)  param的值既可以是本地文件的视屏文件,?#37096;?#20197;是一个视频的网页
        val uri = Uri.parse("images/06A90160456424393C99A40234DCCF4D.mp4")
        //调用系统自带的播放器
        val intent = Intent(Intent.ACTION_VIEW)
        intent.setDataAndType(uri, "video/mp4")
        startActivity(intent)
    }

2、使用VideoView来实现:

布局代码为:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.xingkong.unit8_demo.VideoViewActivity">
    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</android.support.constraint.ConstraintLayout>

实现代码

val uri: Uri? = Uri.parse("images/465C0160E04D7B51F59FA32C5F0A7882.mp4")
        //设置视屏控制器
        videoView.setMediaController(MediaController(this))
        //设置播放视频地址 //可以本地视频和网页视频都可以
        videoView.setVideoURI(uri)
        //开始播放视频
        videoView.start()
        videoView.requestFocus()

3、使用MediaPlayer:

布局代码

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.xingkong.unit8_demo.VideoSurfaceActivity">

    <SurfaceView
        android:id="@+id/video_surface"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"/>

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"/>

    <RelativeLayout
        android:id="@+id/relativeLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:background="#88000000"
        android:gravity="center_vertical"
        android:paddingLeft="5dp"
        android:paddingRight="5dp">

        <Button
            android:id="@+id/button_play"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="暂停"/>

        <Button
            android:id="@+id/button_replay"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@id/button_play"
            android:text="重播"/>

        <Button
            android:id="@+id/button_screenShot"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@id/button_replay"
            android:text="截图"/>

        <Button
            android:id="@+id/button_changeVedioSize"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@id/button_screenShot"
            android:text="全屏">
        </Button>


    </RelativeLayout>

    <TextView
        android:id="@+id/textView_showTime"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/relativeLayout"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_marginBottom="27dp"
        android:text="dasdad"
        android:textColor="@color/colorAccent"/>

    <SeekBar
        android:id="@+id/seekbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/relativeLayout"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"/>
</RelativeLayout>

实现代码:

1.SurfaceView的实现和创建监听

  surface = (findViewById(R.id.video_surface) as SurfaceView?)!!
        holder = surface!!.holder as SurfaceHolder
        // 设置surface回调 
        holder!!.addCallback(this)
        //为了可以播放视频或者使用Camera预览,我们需要指定其Buffer类型
        holder!!!!.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)

2.SurfaceCallback()的实现

    override fun surfaceChanged(arg0: SurfaceHolder, arg1: Int, arg2: Int, arg3: Int) {
        // 当Surface尺寸等参数改变时触发
        Log.v("Surface Change:::", "surfaceChanged called")
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        // 当SurfaceView中的Surface被创建的时候被调用
        //在这里我们指定MediaPlayer在当前的Surface中进行播放
        player!!.setDisplay(holder)
        //在指定了MediaPlayer播放的容器后,我们就可以使用prepare或者prepareAsync来准备播放了
        player!!.prepareAsync()

    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        Log.v("Surface Destory:::", "surfaceDestroyed called")

        if (null != player) {
            player!!.release()
            player = null
        }
    }

3.MediaPlayer的创建和实现

 //下面开始实例化MediaPlayer对象
        player = MediaPlayer()
        // 重置mediaPaly,建议在初始滑mediaplay立即调用。
        player!!.reset()
        // 设置播放完成监听
        player!!.setOnCompletionListener(this)
        // 错误监听回调函数
        player!!.setOnErrorListener(this)
        player!!.setOnInfoListener(this)
        //设置?#25945;?#21152;载完成以后回调函数。
        player!!.setOnPreparedListener(this)
        player!!.setOnSeekCompleteListener(this)
        player!!.setOnVideoSizeChangedListener(this)
        seekbar.setOnSeekBarChangeListener(this)
        //然后指定需要播放文件的路径,初始化MediaPlayer
        dataPath = "images/06A90160456424393C99A40234DCCF4D.mp4"
        try {
            player!!.setDataSource(dataPath)
            Log.v("Next:::", "surfaceDestroyed called")
        } catch (e: IllegalArgumentException) {
            e.printStackTrace()
        } catch (e: IllegalStateException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        }

        //然后,我们取得当前Display对象
        currDisplay = this.windowManager.defaultDisplay

MediaPlayer的创建在代码中,注释比较清楚,特别注意以?#24405;?#28857;:

(1)在MediaPlayer实现以前调用reset()方法,?#20048;筂ediaPlayer的重复。

(2)添加视频的加载的监听,setOnPrepareListener();实现在其方法在加载完成以后播放视频,加载方式又分为两种,同步加载和异步加载。同步方法prepare(),使MediaPlayer进入Prepared加载就绪状态,异步状态使MediaPlayer进入Preparing状态,而内部引擎继续完成未完成的准备工作。

(3)MediaPlayer的setDateSource()?#37096;?#20197;设置为本地播放视频,本次博文设置的网络视频。

4.播放和显示视频

override fun onPrepared(player: MediaPlayer) {
        // 当prepare完成后,该方法触发,在这里我们播放视频
        progressBar.setVisibility(View.GONE);
        seekbar.visibility = View.VISIBLE
        //首先取得video的宽和高
        vWidth = player.videoWidth
        vHeight = player.videoHeight

        if (vWidth > currDisplay!!.width || vHeight > currDisplay!!.height) {
            //如果video的宽或者高超出了当前屏幕的大小,则要进行缩放
            val wRatio = vWidth.toFloat() / currDisplay!!.width.toFloat()
            val hRatio = vHeight.toFloat() / currDisplay!!.height.toFloat()

            //选择大的一个进行缩放
            val ratio = Math.max(wRatio, hRatio)

            vWidth = Math.ceil((vWidth.toFloat() / ratio).toDouble()).toInt()
            vHeight = Math.ceil((vHeight.toFloat() / ratio).toDouble()).toInt()

            //设置surfaceView的布局参数
            surface!!.layoutParams = RelativeLayout.LayoutParams(vWidth, vHeight)
            if (playPosition >= 0) {
                player.seekTo(playPosition);
                playPosition = -1;
            }
            //然后开始播放视频
            player.start()
            // 设置显示到屏幕
            player.setDisplay(holder);
            // 设置surfaceView保持在屏幕上
            player.setScreenOnWhilePlaying(true);
            holder!!.setKeepScreenOn(true);
            // 设?#27599;?#21046;条,放在加载完成以后设置,?#20048;?#33719;取getDuration()错误
            seekbar.setProgress(0);
            seekbar.setMax(player.duration);
            videoTimeString = getShowTime(player.duration.toLong())
            textView_showTime.text = "00:00:00/" + videoTimeString

            seekBarAutoFlag = true;
            // 开启线程 刷新进度条  
            thread.start();

5.播放控制实现

播放时进度条SeekBar的自动控制线程

   /**
     * 滑动条变化线程
     */
    private val thread = object : Thread() {

        override fun run() {
            super.run()
            try {
                while (seekBarAutoFlag) {
                    if (null != player && player!!.isPlaying()) {
                        playPosition = player!!.getCurrentPosition()
                        seekbar.setProgress(playPosition)
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }

        }

    }

SeekBar的变化状态监听

    override fun onStartTrackingTouch(seekBar: SeekBar?) {
    }

    override fun onStopTrackingTouch(seekBar: SeekBar?) {
    }

    override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
        // TODO Auto-generated method stub
        if (progress >= 0) {
            // 如果是用户手动拖动控件,则设置视频跳转。
            if (fromUser) {
                player!!.seekTo(progress)
            }
            // 设置当?#23433;?#25918;时间
            textView_showTime.text = getShowTime(progress.toLong()) + "/" + videoTimeString
        }

    }

视频时间转换方法也就是在上述代码中用到的getShowTime()方法

  /**
     * 转换播放时间
     *
     * @param milliseconds 传入毫秒值
     * @return 返回 hh:mm:ss或mm:ss格式的数据
     */
    @SuppressLint("SimpleDateFormat")
    fun getShowTime(milliseconds: Long): String {
        // 获取日历函数
        val calendar = Calendar.getInstance()
        calendar.setTimeInMillis(milliseconds)
        var dateFormat: SimpleDateFormat? = null
        // 判断是否大于60分钟,如果大于就显示小时。设置?#25484;?#26684;式
        if (milliseconds / 60000 > 60) {
            dateFormat = SimpleDateFormat("hh:mm:ss")
        } else {
            dateFormat = SimpleDateFormat("mm:ss")
        }
        return dateFormat!!.format(calendar.getTime())
    }
设置播放全屏
  // 设置全屏
    // 设置SurfaceView的大小并居中显示
    fun enterFullScreen() {
        val layoutParams = RelativeLayout.LayoutParams(screenWidth,
                screenHeight)
        layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT)
        surface!!.setLayoutParams(layoutParams)
    }

完整代码:


class VideoSurfaceActivity : AppCompatActivity(), MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener, MediaPlayer.OnInfoListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnSeekCompleteListener, MediaPlayer.OnVideoSizeChangedListener, SurfaceHolder.Callback, SeekBar.OnSeekBarChangeListener {


    private var holder: SurfaceHolder? = null
    private var player: MediaPlayer? = null
    private var currDisplay: Display? = null
    private var vWidth: Int = 0
    private var vHeight: Int = 0
    var surface: SurfaceView? = null

    private var playPosition: Int = 0

    private var videoTimeString: String? = null

    private var dataPath: String? = null

    private var screenWidth: Int = 0

    private var screenHeight: Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_video_surface)
        val manager = this.windowManager
        val outMetrics = DisplayMetrics()
        manager.defaultDisplay.getMetrics(outMetrics)
        screenWidth = outMetrics.widthPixels
        screenHeight = outMetrics.heightPixels

        surface = (findViewById(R.id.video_surface) as SurfaceView?)!!
        holder = surface!!.holder as SurfaceHolder
        holder!!.addCallback(this)
        //为了可以播放视频或者使用Camera预览,我们需要指定其Buffer类型
        holder!!!!.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)

        //下面开始实例化MediaPlayer对象
        player = MediaPlayer()
        // 重置mediaPaly,建议在初始滑mediaplay立即调用。
        player!!.reset()
        // 设置播放完成监听
        player!!.setOnCompletionListener(this)
        // 错误监听回调函数
        player!!.setOnErrorListener(this)
        player!!.setOnInfoListener(this)
        //设置?#25945;?#21152;载完成以后回调函数。
        player!!.setOnPreparedListener(this)
        player!!.setOnSeekCompleteListener(this)
        player!!.setOnVideoSizeChangedListener(this)
        seekbar.setOnSeekBarChangeListener(this)
        //然后指定需要播放文件的路径,初始化MediaPlayer
        dataPath = "images/06A90160456424393C99A40234DCCF4D.mp4"
        try {
            player!!.setDataSource(dataPath)
            Log.v("Next:::", "surfaceDestroyed called")
        } catch (e: IllegalArgumentException) {
            e.printStackTrace()
        } catch (e: IllegalStateException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        }

        //然后,我们取得当前Display对象
        currDisplay = this.windowManager.defaultDisplay


        // 暂停和播放
        button_play.setOnClickListener(View.OnClickListener {

            if (player!!.isPlaying) {
                player!!.pause()
            } else {
                player!!.start()
            }

        })
        // 重新播放
        button_replay.setOnClickListener(View.OnClickListener {
            player!!.seekTo(0)
            player!!.start()
            player!!.setDisplay(holder);
            // 设置surfaceView保持在屏幕上
            player!!.setScreenOnWhilePlaying(true);
            holder!!.setKeepScreenOn(true);

        })
        button_screenShot.setOnClickListener(View.OnClickListener {
            savaScreenShot(playPosition.toLong())
        })


        button_changeVedioSize.setOnClickListener(View.OnClickListener {
            enterFullScreen()
        })

    }


    override fun surfaceChanged(arg0: SurfaceHolder, arg1: Int, arg2: Int, arg3: Int) {
        // 当Surface尺寸等参数改变时触发
        Log.v("Surface Change:::", "surfaceChanged called")
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        // 当SurfaceView中的Surface被创建的时候被调用
        //在这里我们指定MediaPlayer在当前的Surface中进行播放
        player!!.setDisplay(holder)
        //在指定了MediaPlayer播放的容器后,我们就可以使用prepare或者prepareAsync来准备播放了
        player!!.prepareAsync()

    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        Log.v("Surface Destory:::", "surfaceDestroyed called")

        if (null != player) {
            player!!.release()
            player = null
        }
    }

    override fun onVideoSizeChanged(arg0: MediaPlayer, arg1: Int, arg2: Int) {
        // 当video大小改变时触发
        //这个方法在设置player的source后至少触发一次
        Log.v("Video Size Change", "onVideoSizeChanged called")
    }

    override fun onSeekComplete(arg0: MediaPlayer) {
        // seek操作完成时触发
        Log.v("Seek Completion", "onSeekComplete called")
    }

    private var seekBarAutoFlag: Boolean = false

    override fun onPrepared(player: MediaPlayer) {
        // 当prepare完成后,该方法触发,在这里我们播放视频
        progressBar.setVisibility(View.GONE);
        seekbar.visibility = View.VISIBLE
        //首先取得video的宽和高
        vWidth = player.videoWidth
        vHeight = player.videoHeight

        if (vWidth > currDisplay!!.width || vHeight > currDisplay!!.height) {
            //如果video的宽或者高超出了当前屏幕的大小,则要进行缩放
            val wRatio = vWidth.toFloat() / currDisplay!!.width.toFloat()
            val hRatio = vHeight.toFloat() / currDisplay!!.height.toFloat()

            //选择大的一个进行缩放
            val ratio = Math.max(wRatio, hRatio)

            vWidth = Math.ceil((vWidth.toFloat() / ratio).toDouble()).toInt()
            vHeight = Math.ceil((vHeight.toFloat() / ratio).toDouble()).toInt()

            //设置surfaceView的布局参数
            surface!!.layoutParams = RelativeLayout.LayoutParams(vWidth, vHeight)
            if (playPosition >= 0) {
                player.seekTo(playPosition);
                playPosition = -1;
            }
            //然后开始播放视频
            player.start()
            // 设置显示到屏幕
            player.setDisplay(holder);
            // 设置surfaceView保持在屏幕上
            player.setScreenOnWhilePlaying(true);
            holder!!.setKeepScreenOn(true);
            // 设?#27599;?#21046;条,放在加载完成以后设置,?#20048;?#33719;取getDuration()错误
            seekbar.setProgress(0);
            seekbar.setMax(player.duration);
            videoTimeString = getShowTime(player.duration.toLong())
            textView_showTime.text = "00:00:00/" + videoTimeString

            seekBarAutoFlag = true;
            // 开启线程 刷新进度条  
            thread.start();


        }
    }

    /**
     * 滑动条变化线程
     */
    private val thread = object : Thread() {

        override fun run() {
            super.run()
            try {
                while (seekBarAutoFlag) {
                    if (null != player && player!!.isPlaying()) {
                        playPosition = player!!.getCurrentPosition()
                        seekbar.setProgress(playPosition)
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }

        }

    }

    override fun onStartTrackingTouch(seekBar: SeekBar?) {
    }

    override fun onStopTrackingTouch(seekBar: SeekBar?) {
    }

    override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
        // TODO Auto-generated method stub
        if (progress >= 0) {
            // 如果是用户手动拖动控件,则设置视频跳转。
            if (fromUser) {
                player!!.seekTo(progress)
            }
            // 设置当?#23433;?#25918;时间
            textView_showTime.text = getShowTime(progress.toLong()) + "/" + videoTimeString
        }

    }

    override fun onInfo(player: MediaPlayer, whatInfo: Int, extra: Int): Boolean {
        // 当一些特定信息出现或者警告时触发
        when (whatInfo) {
            MediaPlayer.MEDIA_INFO_BAD_INTERLEAVING -> {
            }
            MediaPlayer.MEDIA_INFO_METADATA_UPDATE -> {
            }
            MediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING -> {
            }
            MediaPlayer.MEDIA_INFO_NOT_SEEKABLE -> {
            }
        }
        return false
    }

    override fun onError(player: MediaPlayer, whatError: Int, extra: Int): Boolean {
        Log.v("Play Error:::", "onError called")
        when (whatError) {
            MediaPlayer.MEDIA_ERROR_SERVER_DIED -> Log.v("Play Error:::", "MEDIA_ERROR_SERVER_DIED")
            MediaPlayer.MEDIA_ERROR_UNKNOWN -> Log.v("Play Error:::", "MEDIA_ERROR_UNKNOWN")
            else -> {
            }
        }
        return false
    }

    override fun onCompletion(player: MediaPlayer) {
        // 当MediaPlayer播放完成后触发
        Log.v("onCompletion", "播放完成了")
        //this.finish()
    }

    /**
     * 转换播放时间
     *
     * @param milliseconds 传入毫秒值
     * @return 返回 hh:mm:ss或mm:ss格式的数据
     */
    @SuppressLint("SimpleDateFormat")
    fun getShowTime(milliseconds: Long): String {
        // 获取日历函数
        val calendar = Calendar.getInstance()
        calendar.setTimeInMillis(milliseconds)
        var dateFormat: SimpleDateFormat? = null
        // 判断是否大于60分钟,如果大于就显示小时。设置?#25484;?#26684;式
        if (milliseconds / 60000 > 60) {
            dateFormat = SimpleDateFormat("hh:mm:ss")
        } else {
            dateFormat = SimpleDateFormat("mm:ss")
        }
        return dateFormat!!.format(calendar.getTime())
    }


    /**
     * 保存视?#21040;?#22270;.该方法只能支持本地视频文件
     *
     * @param time视频当前位置
     */
    fun savaScreenShot(time: Long) {
        // 标记是否保存成功  
        var isSave = false
        // 获取文件路径  
        var path: String? = null
        // 文件名称  
        var fileName: String? = null
        if (time >= 0) {
            try {
                val mediaMetadataRetriever = MediaMetadataRetriever()
                mediaMetadataRetriever.setDataSource(dataPath)
                // 获取视频的播放总时长单位为毫秒  
                val timeString = mediaMetadataRetriever
                        .extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
                // 转换格式为微秒  
                val timelong = java.lang.Long.parseLong(timeString) * 1000
                // 计算当前视?#21040;?#21462;的位置  
                val index = time * timelong / player!!.getDuration()
                // 获取当前视频指定位置的截图,时间参数的单位是微秒,做了*1000处理  
                // 第二个参数为指定位置,意思接近的位?#23186;?#22270;  
                val bitmap = mediaMetadataRetriever.getFrameAtTime(time * 1000,
                        MediaMetadataRetriever.OPTION_CLOSEST_SYNC)
                // ?#22836;拋试? 
                mediaMetadataRetriever.release()
                // 判断外部设备SD卡是否存在  
                if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                    // 存在获取外部文件路径  
                    path = Environment.getExternalStorageDirectory().getPath()
                } else {
                    // 不存在获取内部存储  
                    path = Environment.getDataDirectory().getPath()
                }
                // 设置文件名称 ,以事件毫秒为名称  
                fileName = Calendar.getInstance().timeInMillis.toString() + ".jpg"
                // 设置保存文件  
                val file = File(path + "/shen/" + fileName)

                if (!file.exists()) {
                    file.createNewFile()
                }
                val fileOutputStream = FileOutputStream(file)
                bitmap.compress(CompressFormat.JPEG, 100, fileOutputStream)
                isSave = true
            } catch (e: Exception) {
                e.printStackTrace()
            }

            // 保存成功以后,展示图片  
            if (isSave) {
                val imageView = ImageView(this)
                imageView.setLayoutParams(ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT))
                imageView.setImageBitmap(BitmapFactory.decodeFile(path + "/shen/" + fileName))
                AlertDialog.Builder(this).setView(imageView).show()
            }
        }

    }

    // 设置全屏
    // 设置SurfaceView的大小并居中显示
    fun enterFullScreen() {
        val layoutParams = RelativeLayout.LayoutParams(screenWidth,
                screenHeight)
        layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT)
        surface!!.setLayoutParams(layoutParams)
    }
}

8.6 小结与点评

1.通知

Notification,俗称,是一种具有全?#20013;?#26524;的通知,它展示在屏幕的顶端,首先会表现为一个图标的?#38382;劍?#24403;用户向下滑动的时候,展示出通知具体的内容。   因为Android版本的兼容性问题,对于Notification而言,Android3.0是一个分水岭,在其之前构建Notification推荐使用Notification.Builder构建,而在Android3.0之后,一般推荐使用NotificationCompat.Builder构建。  

2.兼容和权限

多?#25945;?#22312;实际的项目中的要做好兼容性问题,比如相机问题,android23以下,android23,以及android24以上都要分别做兼容.以及权限的设置


欢迎大家对本教程的内容进行刊错和纠正,点击此处?#24052;?#35770;坛给你?#19981;?#30340;文章作者给予支持鼓励,若有问题反馈和建议请注明具体章节
西甲2015赛程表 埃及旋转试玩 福建十一选五走势图图一定牛 企鹅家族免费试玩 Ag电子游戏如何破解 广东快乐10分计划 猴子基诺APP下载 船奴塔什干火车头 现金咖啡登陆 肖恩·马里昂 波斯波利斯棉衣