Android TV 开发之 TV视频播放器

Android TV视频播放器VideoView

不想往下看可以直接在GitHub上面克隆到自己的项目中
GitHub地址

闲谈

  最近公司又给了一个新任务,说要做电视机顶盒开发,这个机顶盒开发之前也没有接触过啊,没经验,这使我走了很多坑,写这个日志就是帮助和自己一样的新手开发代码顺利一些,少走弯路,如果你是有经验的,请无视我的废话和文章,因为我将从创建项目开始写这篇日志,这意味着会比较无聊,你要有心理准备,当然如果你是一个新手的话,恰好最近又要做AndroidTV的开发,那么你就来对地方了,好了,话不多说了,进入正题吧。

简介

  Android TV 开发,顾名思义也就是电视开发,说的高端点就是智能电视,相信你们家里都有吧,不要说你家至今用着十几年前的老电视,那我无话可说了,TV开发的资源我从网上找到的都是一些零零碎碎的,不够完整,而有一些项目还要你给积分才能去下载看,不够开源,痛定思痛,我决定自己弄一个开源的项目出来,自己来维护,学习中开发,也有可能TV这方面的文章我还会写,也有可能只写这一篇,接下来我们从创建TV项目开始。

正题

开发准备:
  电脑(笔记本、台式都行)、JDK环境变量配置(PS:因为是Java写的,想了解Kotlin的可以和我沟通)、Android Studio3.5(开发软件)。

创建项目:
第一步:File → New → New Project
在这里插入图片描述
第二步:选择TV 然后创建一个空的项目也就是点左边的 Add No Activity(PS:为什么不用谷歌的框架呢,因为这个第一次我觉得自己从头来一遍会比较好,这样更有帮助,后面你再用这个框架,而且这个空项目进去之后在,AndroidManifest.xml里面还是要添加leanback的,下面继续吧)
在这里插入图片描述
第三步:确定你的开发信息无误之后点击Finish,创建这个项目(Android Studio3.5中开发TV 最低的API版本为5.0,低于这个版本则不能正常运行)
在这里插入图片描述
第四步:然后我们打开这个项目
在这里插入图片描述
第五步:可以看到AndroidManifest.xml文件中没有运行Main的一个主活动,所以上面的是机器人头会有一个红色的小×,然后创建一个MainActivity之后再改动这个文件。
在这里插入图片描述
第六步:鼠标右键你的包名 → New → Activity → Empty Activity(空的活动)
在这里插入图片描述
在这里插入图片描述
上图中我勾选了Launcher Activity(勾中的意思就是把你的这个Activity作为运行的主入口,默认是不勾中的,因为我的AndroidManifest.xml文件中没有主运行活动,所以我勾中),然后点击Finish

在这里插入图片描述
创建好之后打开AndroidManifest.xml文件可以看到,中间多个一部分配置内容,主要的意思就是将MainActivity这个活作为启动入口,然后可以看到上方的mainfest下面有一条红色波浪线,这说明有问题,然后具体看看是什么问题。
鼠标放在上面可以看到有一个tip,
在这里插入图片描述
意思很明显要我们配置leanback眼熟不? 然后我们鼠标点击这个mainfest使用快捷键Alt + Enter 引入配置,引入三次之后发现不报错了
在这里插入图片描述
这个时候这个项目已经初步搭建完成了,(PS:我是不是很啰嗦啊)

配置项目:
你以为这个时候你就能运行了吗?不,你不能,首先你得找一台虚拟机或者一台真机,我这里用的是真机,然后用usb线连接上你的电脑,然后打开这个机顶盒的开发者模式,再启用usb调试,这个时候你的Android Studio中就会有读取到你的设备名称(PS:这一块有时候比较看人品,不见得你就能成功,所以我成功了!)
在这里插入图片描述
这是我的设备名称,接下来就是配置项目的build.gradle,在android的闭包里面加入:

compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}

然后在dependencies闭包中加入:(这几句代码有什么用呢,因为我不想再写findById了,所以用BindView的方式来做)

//butterknifeimplementation 'com.jakewharton:butterknife:10.1.0'annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'

build.gradle的完整代码如下:

apply plugin: 'com.android.application'android {compileSdkVersion 29buildToolsVersion "29.0.3"defaultConfig {applicationId "com.llw.androidtvdemo"minSdkVersion 21targetSdkVersion 29versionCode 1versionName "1.0"}compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}
}dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])implementation 'androidx.leanback:leanback:1.0.0'implementation 'androidx.appcompat:appcompat:1.1.0'implementation 'androidx.constraintlayout:constraintlayout:1.1.3'//butterknifeimplementation 'com.jakewharton:butterknife:10.1.0'annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
}

build.gradle改动代码之后记得sync一下啊,否则改动无效的。

加入上述代码后,点击File → Settings → Plugins → Marketolace → 输入butterknife然后搜索 → 再下载安装 安装之后会提醒你重启AS(PS:Android Studio的简称)
重启之后我们再layout_main.xml文件中写一点东西
在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"android:orientation="vertical"android:gravity="center"tools:context=".MainActivity"><TextViewandroid:id="@+id/tv_test"android:text="Hello TV"android:layout_width="wrap_content"android:layout_height="wrap_content"/><Button android:layout_marginTop="20dp"android:id="@+id/btn_test"android:text="TV"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
</LinearLayout>

布局很简单,纵向线性布局,里面是一个TextView和Button,然后我们换成横屏的布局预览效果
在这里插入图片描述
点击Preview就可以看到你的预览布局,然后点击那个下拉窗口,选择TV,
在这里插入图片描述
TV有1920 X 1080和 1280 X 720两种尺寸的,这也是市面上常用的,当然你也可以通过custom来自定义,这里我们选择TV 720p的,这时候我们打开MainActivity.java文件,然后选中布局文件,鼠标右键,点击Generate生成也可通过快捷键 Alt + Insert
在这里插入图片描述
然后会弹出一个小窗口
在这里插入图片描述
点击之后会出现如下弹窗,可以实例化控件并控件添加点击事件,这里给按钮添加一个点击事件,然后我们看一下MainActivity.java中的代码,并在点击的时候弹出一个Toast消息
在这里插入图片描述
MainActivity.java

package com.llw.androidtvdemo;import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;public class MainActivity extends AppCompatActivity {@BindView(R.id.tv_test)TextView tvTest;@BindView(R.id.btn_test)Button btnTest;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ButterKnife.bind(this);}@OnClick(R.id.btn_test)public void onViewClicked() {//Toast 提示Toast.makeText(this,tvTest.getText().toString(),Toast.LENGTH_SHORT).show();}
}

这个时候你应该迫不及待的想要运行一下了吧,我们还有一步就是主题的设置
打开values下面的styles.xml文件
在这里插入图片描述
我们不用它这个主题,重新创建一个

 <!-- Base application theme. --><style name="AppTheme2" parent="Theme.AppCompat.Light.NoActionBar"><!-- Customize your theme here. --><item name="colorPrimary">@color/colorPrimary</item><item name="colorPrimaryDark">@color/colorPrimaryDark</item><item name="colorAccent">@color/colorAccent</item></style>

styles.xml的完整代码如下:

<?xml version="1.0" encoding="utf-8"?>
<resources><style name="AppTheme" parent="@style/Theme.Leanback" /><!-- Base application theme. --><style name="AppTheme2" parent="Theme.AppCompat.Light.NoActionBar"><!-- Customize your theme here. --><item name="colorPrimary">@color/colorPrimary</item><item name="colorPrimaryDark">@color/colorPrimaryDark</item><item name="colorAccent">@color/colorAccent</item></style></resources>

然后你会发现少几个颜色,这时候我们在values文件夹下面创建一个colors.xml的文件
colors.xml代码如下:

<?xml version="1.0" encoding="utf-8"?>
<resources><color name="colorPrimary">#008577</color><color name="colorPrimaryDark">#00574B</color><color name="colorAccent">#D81B60</color><color name="tx_onclick">#1EBADE</color>
</resources>

然后就要使用我们新创建的主题了,打开AndroidManifest.xml文件
在这里插入图片描述
修改,AppTheme,改成AppTheme2,然后运行项目.运行效果如下图
在这里插入图片描述
这个时候你没有想过,我怎么点击这个按钮呢?电视机都是用遥控器的啊,遥控器又怎么操作呢?
这些问题一定在你的脑海里面环绕着,我们注意到,电视机使用遥控器,而我们的手机使用手指触摸点击,这个不能混为一谈,所以电视上需要用到焦点电视上都是通过控件获取焦点来实现点击效果的,我们在布局文件的button中写入

android:focusable="true"

意思就是可以获取到焦点,为false则不可获取焦点,
在代码里

btnTest.setFocusable(true);

为false则不可获取焦点。
在已知控件ID的情况下我们可以设置上下左右的移动控件,

		android:nextFocusUp="@id/tv_test"android:nextFocusDown="@id/tv_test"android:nextFocusLeft="@id/tv_test"android:nextFocusRight="@id/tv_test"

代码中:

		btnTest.setNextFocusUpId(R.id.tv_test);btnTest.setNextFocusDownId(R.id.tv_test);btnTest.setNextFocusLeftId(R.id.tv_test);btnTest.setNextFocusRightId(R.id.tv_test);

了解这个之后,我们还得知道遥控器的按键监听,毕竟是用遥控器来操作的啊,按键监听代码如下:

private String TAG = "key";@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {switch (keyCode) {case KeyEvent.KEYCODE_ENTER:     //确定键entercase KeyEvent.KEYCODE_DPAD_CENTER:Log.d(TAG, "enter--->");break;case KeyEvent.KEYCODE_BACK:    //返回键Log.d(TAG,"back--->");return true;   //这里由于break会退出,所以我们自己要处理掉 不返回上一层case KeyEvent.KEYCODE_SETTINGS: //设置键Log.d(TAG, "setting--->");break;case KeyEvent.KEYCODE_DPAD_DOWN:   //向下键/*    实际开发中有时候会触发两次,所以要判断一下按下时触发 ,松开按键时不触发*    exp:KeyEvent.ACTION_UP*/if (event.getAction() == KeyEvent.ACTION_DOWN) {Log.d(TAG, "down--->");}break;case KeyEvent.KEYCODE_DPAD_UP:   //向上键Log.d(TAG, "up--->");break;case KeyEvent.KEYCODE_0:   //数字键0Log.d(TAG, "0--->");break;case KeyEvent.KEYCODE_DPAD_LEFT: //向左键Log.d(TAG, "left--->");break;case KeyEvent.KEYCODE_DPAD_RIGHT:  //向右键Log.d(TAG, "right--->");break;case KeyEvent.KEYCODE_INFO:    //info键Log.d(TAG, "info--->");break;case KeyEvent.KEYCODE_PAGE_DOWN:     //向上翻页键case KeyEvent.KEYCODE_MEDIA_NEXT:Log.d(TAG, "page down--->");break;case KeyEvent.KEYCODE_PAGE_UP:     //向下翻页键case KeyEvent.KEYCODE_MEDIA_PREVIOUS:Log.d(TAG, "page up--->");break;case KeyEvent.KEYCODE_VOLUME_UP:   //调大声音键Log.d(TAG, "voice up--->");break;case KeyEvent.KEYCODE_VOLUME_DOWN: //降低声音键Log.d(TAG, "voice down--->");break;case KeyEvent.KEYCODE_VOLUME_MUTE: //禁用声音Log.d(TAG, "voice mute--->");break;default:break;}return super.onKeyDown(keyCode, event);}

如果你要监听Home键的话,就需要通过广播来,
在MainActivity中创建一个class

class  HomeReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();if(action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)){String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);if(SYSTEM_DIALOG_REASON_HOME_KEY.equals(reason)){Toast.makeText(MainActivity.this,"home键触发",Toast.LENGTH_SHORT).show();Log.d(TAG, "home键触发");}}}}

在onCreate()方法中注册广播,只要调用initReceiver()方法即可

	public final String SYSTEM_DIALOG_REASON_KEY = "reason";public final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";private HomeReceiver homeReceiver;/*** 注册广播*/private void initReceiver() {homeReceiver = new HomeReceiver();IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);registerReceiver(homeReceiver, filter);}

页面销毁时,注销掉广播

@Overrideprotected void onDestroy() {super.onDestroy();if(homeReceiver!=null){unregisterReceiver(homeReceiver);}}

这段代码我也是从网上找的,
然后我们在确定键的下面弹出这个Toast

case KeyEvent.KEYCODE_ENTER:     //确定键entercase KeyEvent.KEYCODE_DPAD_CENTER:Log.d(TAG, "enter--->");Toast.makeText(this,tvTest.getText().toString(),Toast.LENGTH_SHORT).show();break;

运行效果如下:
在这里插入图片描述
MainActivity.java完整代码如下:

package com.llw.androidtvdemo;import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;public class MainActivity extends AppCompatActivity {@BindView(R.id.tv_test)TextView tvTest;@BindView(R.id.btn_test)Button btnTest;public final String SYSTEM_DIALOG_REASON_KEY = "reason";public final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";private HomeReceiver homeReceiver;/*** 注册广播*/private void initReceiver() {homeReceiver = new HomeReceiver();IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);registerReceiver(homeReceiver, filter);}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ButterKnife.bind(this);btnTest.setFocusable(true);initReceiver();}@OnClick(R.id.btn_test)public void onViewClicked() {//Toast 提示Toast.makeText(this,tvTest.getText().toString(),Toast.LENGTH_SHORT).show();}class  HomeReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();if(action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)){String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);if(SYSTEM_DIALOG_REASON_HOME_KEY.equals(reason)){Toast.makeText(MainActivity.this,"home键触发",Toast.LENGTH_SHORT).show();Log.d(TAG, "home键触发");}}}}private String TAG = "key";@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {switch (keyCode) {case KeyEvent.KEYCODE_ENTER:     //确定键entercase KeyEvent.KEYCODE_DPAD_CENTER:Log.d(TAG, "enter--->");Toast.makeText(this,tvTest.getText().toString(),Toast.LENGTH_SHORT).show();break;case KeyEvent.KEYCODE_BACK:    //返回键Log.d(TAG,"back--->");return true;   //这里由于break会退出,所以我们自己要处理掉 不返回上一层case KeyEvent.KEYCODE_SETTINGS: //设置键Log.d(TAG, "setting--->");break;case KeyEvent.KEYCODE_DPAD_DOWN:   //向下键/*    实际开发中有时候会触发两次,所以要判断一下按下时触发 ,松开按键时不触发*    exp:KeyEvent.ACTION_UP*/if (event.getAction() == KeyEvent.ACTION_DOWN) {Log.d(TAG, "down--->");}break;case KeyEvent.KEYCODE_DPAD_UP:   //向上键Log.d(TAG, "up--->");break;case KeyEvent.KEYCODE_0:   //数字键0Log.d(TAG, "0--->");break;case KeyEvent.KEYCODE_DPAD_LEFT: //向左键Log.d(TAG, "left--->");break;case KeyEvent.KEYCODE_DPAD_RIGHT:  //向右键Log.d(TAG, "right--->");break;case KeyEvent.KEYCODE_INFO:    //info键Log.d(TAG, "info--->");break;case KeyEvent.KEYCODE_PAGE_DOWN:     //向上翻页键case KeyEvent.KEYCODE_MEDIA_NEXT:Log.d(TAG, "page down--->");break;case KeyEvent.KEYCODE_PAGE_UP:     //向下翻页键case KeyEvent.KEYCODE_MEDIA_PREVIOUS:Log.d(TAG, "page up--->");break;case KeyEvent.KEYCODE_VOLUME_UP:   //调大声音键Log.d(TAG, "voice up--->");break;case KeyEvent.KEYCODE_VOLUME_DOWN: //降低声音键Log.d(TAG, "voice down--->");break;case KeyEvent.KEYCODE_VOLUME_MUTE: //禁用声音Log.d(TAG, "voice mute--->");break;default:break;}return super.onKeyDown(keyCode, event);}@Overrideprotected void onDestroy() {super.onDestroy();if(homeReceiver!=null){unregisterReceiver(homeReceiver);}}}

然后我们就要想一下编码的过程和逻辑问题了,
1.播放视频的来源 本地 和 网络
2.播放视频的的停止播放、继续播放、重新播放
3.播放视频时的时间和进度计算
4.播放时候按遥控器左右键时,前进 后退
先想清楚这些问题,才能使编码过程中变得有条理

视频来源

本地:
我们可以在valuse文件夹下面创建一个raw文件夹,在里面放一个mp4短视频文件,(PS:至于在真机存储里面放一个视频,你只要播放路径指定这个视频所在地址,然后再加上文件的读写权限,因为我不是这么实现的,所以就不过多赘述了
网络:
就是通过一个视频地址来播放视频,既然是通过网络来播放的,我们肯定要有联网的权限啊,在AndroidManifest.xml文件中添加联网许可权限
如下所示

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

布局文件

<?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"android:orientation="vertical"android:gravity="center"tools:context=".MainActivity"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><!--自定义的VideoView  做了绘制改变,和网络地址许可--><com.llw.androidtvdemo.view.MyVideoViewandroid:id="@+id/video_view"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_gravity="center" /></LinearLayout><!--底部控制栏  开始时间 进度条 结束时间--><RelativeLayoutandroid:background="@drawable/shape_gradual_change"android:layout_alignParentBottom="true"android:layout_width="match_parent"android:layout_height="@dimen/dp_100"><LinearLayoutandroid:gravity="center_vertical"android:layout_margin="@dimen/dp_10"android:layout_alignParentBottom="true"android:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:id="@+id/tv_play_time"android:text="00:00"android:textSize="@dimen/sp_24"android:textColor="@color/white"android:layout_width="wrap_content"android:layout_height="wrap_content"/><SeekBarandroid:layout_marginLeft="@dimen/dp_20"android:layout_marginRight="@dimen/dp_20"android:id="@+id/time_seekBar"android:layout_width="0dp"android:layout_weight="1"android:layout_height="wrap_content"android:layout_centerInParent="true"android:max="100"android:maxHeight="3dp"android:minHeight="3dp"android:progress="0"android:progressDrawable="@drawable/seekbar_style"android:thumb="@drawable/thumb" /><TextViewandroid:id="@+id/tv_total_time"android:text="00:00"android:textSize="@dimen/sp_24"android:textColor="@color/white"android:layout_width="wrap_content"android:layout_height="wrap_content"/></LinearLayout></RelativeLayout><!--视频结束时 显示黑色背景--><RelativeLayoutandroid:visibility="gone"android:id="@+id/lay_finish_bg"android:background="#000"android:layout_width="match_parent"android:layout_height="match_parent"/><!--视频播放中 控制暂停和播放的按钮--><ImageButtonandroid:visibility="gone"android:focusable="true"android:layout_centerInParent="true"android:id="@+id/btn_play_or_pause"android:background="@mipmap/icon_pause"android:layout_width="@dimen/dp_100"android:layout_height="@dimen/dp_100"/><!--视频结束时 显示重播图标--><ImageButtonandroid:visibility="gone"android:layout_centerInParent="true"android:id="@+id/btn_restart_play"android:background="@mipmap/icon_restart_play"android:layout_width="@dimen/dp_100"android:layout_height="@dimen/dp_100"/>
</RelativeLayout>

注释已经加在布局文件里面了,下面就不过多讲述了,布局文件中的自定义VideoView代码如下:

package com.llw.androidtvdemo.view;import android.content.Context;
import android.net.Uri;
import android.util.AttributeSet;
import android.widget.VideoView;import com.llw.androidtvdemo.view.util.SSlUtiles;import javax.net.ssl.HttpsURLConnection;/*** 自定义VideoView*/
public class MyVideoView extends VideoView {public MyVideoView(Context context) {super(context);}public MyVideoView(Context context, AttributeSet attrs) {super(context, attrs);}public MyVideoView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//super.onMeasure(widthMeasureSpec, heightMeasureSpec);int width = getDefaultSize(getWidth(), widthMeasureSpec);int height = getDefaultSize(getHeight(), heightMeasureSpec);setMeasuredDimension(width, height);}@Overridepublic void setVideoURI(Uri uri) {super.setVideoURI(uri);try {HttpsURLConnection.setDefaultSSLSocketFactory(SSlUtiles.createSSLSocketFactory());HttpsURLConnection.setDefaultHostnameVerifier(new SSlUtiles.TrustAllHostnameVerifier());} catch (Exception e) {e.printStackTrace();}}}

自定义VideoView中SSlUtils网络证书许可类代码如下:

package com.llw.androidtvdemo.view.util;import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;public class SSlUtils {/*** 默认信任所有的证书** xts*/public static SSLSocketFactory createSSLSocketFactory() {SSLSocketFactory sSLSocketFactory = null;try {SSLContext sc = SSLContext.getInstance("TLS");sc.init(null, new TrustManager[] { (TrustManager) new TrustAllManager() }, new SecureRandom());sSLSocketFactory = sc.getSocketFactory();} catch (Exception e) {}return sSLSocketFactory;}public static class TrustAllManager implements X509TrustManager {@Overridepublic void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}@Overridepublic void checkServerTrusted(X509Certificate[] chain, String authType)throws CertificateException {}@Overridepublic X509Certificate[] getAcceptedIssuers() {return new X509Certificate[0];}}public static class TrustAllHostnameVerifier implements HostnameVerifier {public boolean verify(String hostname, SSLSession session) {return true;}}}

这个类主要是针对于 VideoView 无法播放此视频 问题,如果你没有这个问题的话,可以在MyVideoView去掉下面这一段代码:

@Overridepublic void setVideoURI(Uri uri) {super.setVideoURI(uri);try {HttpsURLConnection.setDefaultSSLSocketFactory(SSlUtils.createSSLSocketFactory());HttpsURLConnection.setDefaultHostnameVerifier(new SSlUtils.TrustAllHostnameVerifier());} catch (Exception e) {e.printStackTrace();}}

然后来看MainActivity中的代码,通过注解的方式我的控件已经不需要声明和findById了。
首先配置一下我们的VideoVIew

/*** 初始化VideoView*/private void initVideo() {//本地视频
//        videoView.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/raw/test"));//网络视频final Uri uri = Uri.parse("http://gslb.miaopai.com/stream/ed5HCfnhovu3tyIQAiv60Q__.mp4");videoView.setVideoURI(uri);videoView.requestFocus();videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {int totalTime = videoView.getDuration();//获取视频的总时长tvTotalTime.setText(stringForTime(totalTime));//设置视频总时间,stringForTime是写的一个时间装换方法,下面会提到// 开始线程,更新进度条的刻度handler.postDelayed(runnable, 0);timeSeekBar.setMax(videoView.getDuration());//视频加载完成,准备好播放视频的回调videoView.start();}});}

上面的初始化中用到了一个线程,线程代码如下:

private Handler handler = new Handler();private Runnable runnable = new Runnable() {public void run() {if (videoView.isPlaying()) {int current = videoView.getCurrentPosition();//获取播放过程中位置timeSeekBar.setProgress(current);//设置进度条的位置tvPlayTime.setText(time(videoView.getCurrentPosition()));//播放过程中的时间}handler.postDelayed(runnable, 500);//播放过程中0.5秒执行一次}};

然后是onCreate()方法

@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ButterKnife.bind(this);//注释然后自动添加的timeSeekBar.setOnSeekBarChangeListener(onSeekBarChangeListener);//添加进度条的变化监听initVideo();//初始化VideoView//videoView播放完成监听videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {key = 1;//这是一个全局变量,用于控制遥控单击确定或者ok键时,是暂停继续还是重新播放,1则是重新播放视频btnRestartPlay.setVisibility(View.VISIBLE);//显示黑色背景,布局文件中注释提到了layFinishBg.setVisibility(View.VISIBLE);//显示白色重播图标,布局文件中注释提到了}});//videoView播放异常监听,类似于  此视频无法播放  这样的错误提示videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {Toast.makeText(MainActivity.this, "播放出错", Toast.LENGTH_SHORT).show();return false;}});}

代码中用到的一个方法:

/*** 时间转换方法* @param millionSeconds* @return*/protected String time(long millionSeconds) {SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");Calendar c = Calendar.getInstance();c.setTimeInMillis(millionSeconds);return simpleDateFormat.format(c.getTime());}

控制视频是 播放还是暂停 或者是重播

/*** 控制视频是  播放还是暂停  或者是重播* @param isPlay* @param keys*/private void isVideoPlay(boolean isPlay, int keys) {switch (keys) {case 0:if (isPlay) {//暂停btnPlayOrPause.setBackground(getResources().getDrawable(R.mipmap.icon_player));//更换按钮的图标并显示出来btnPlayOrPause.setVisibility(View.VISIBLE);videoView.pause();} else {//继续播放btnPlayOrPause.setBackground(getResources().getDrawable(R.mipmap.icon_pause));//更换按钮的图标并显示出来btnPlayOrPause.setVisibility(View.VISIBLE);// 开始线程,更新进度条的刻度handler.postDelayed(runnable, 0);videoView.start();//继续播放timeSeekBar.setMax(videoView.getDuration());timeGone();//当我们选择继续播放之后,就不能让这个图标一直显示下去,但是又不能马上消失,这样很突兀,所以用了延时1.5秒隐藏,比较合理,这个方法后面会贴出来。}break;case 1://重新播放initVideo();btnRestartPlay.setVisibility(View.GONE);//白色重播图标隐藏layFinishBg.setVisibility(View.GONE);//黑色背景隐藏key = 0;//重新播放之后,我们再将key置为0,这样就不会影响到下一次视频播放过程中的暂停和继续的监听操作了break;}

延时1.5秒隐藏

private void timeGone() {new Handler().postDelayed(new Runnable() {@Overridepublic void run() {btnPlayOrPause.setVisibility(View.INVISIBLE);}}, 1500);}

进度条监听

/*** 进度条监听*/private SeekBar.OnSeekBarChangeListener onSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {// 当进度条停止修改的时候触发@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {// 取得当前进度条的刻度int progress = seekBar.getProgress();if (videoView.isPlaying()) {// 设置当前播放的位置videoView.seekTo(progress);}}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}@Overridepublic void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {}};

时间转换方法

//将长度转换为时间StringBuilder mFormatBuilder = new StringBuilder();Formatter mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());//整数类型转换为时分秒private String stringForTime(int timeMs) {int totalSeconds = timeMs / 1000;int seconds = totalSeconds % 60;int minutes = (totalSeconds / 60) % 60;int hours = totalSeconds / 3600;mFormatBuilder.setLength(0);if (hours > 0) {return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();} else {return mFormatter.format("%02d:%02d", minutes, seconds).toString();}}

遥控器按键监听

private String TAG = "key";/*** 遥控器按键监听* @param keyCode* @param event* @return*/@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {switch (keyCode) {case KeyEvent.KEYCODE_ENTER:     //确定键entercase KeyEvent.KEYCODE_DPAD_CENTER:Log.d(TAG, "enter--->");//如果是播放中则暂停、如果是暂停则继续播放isVideoPlay(videoView.isPlaying(), key);break;case KeyEvent.KEYCODE_BACK:    //返回键Log.d(TAG,"back--->");return true;   //这里由于break会退出,所以我们自己要处理掉 不返回上一层case KeyEvent.KEYCODE_SETTINGS: //设置键Log.d(TAG, "setting--->");break;case KeyEvent.KEYCODE_DPAD_DOWN:   //向下键/*    实际开发中有时候会触发两次,所以要判断一下按下时触发 ,松开按键时不触发*    exp:KeyEvent.ACTION_UP*/if (event.getAction() == KeyEvent.ACTION_DOWN) {Log.d(TAG, "down--->");}break;case KeyEvent.KEYCODE_DPAD_UP:   //向上键Log.d(TAG, "up--->");break;case KeyEvent.KEYCODE_DPAD_LEFT: //向左键Log.d(TAG, "left--->");if (videoView.getCurrentPosition() > 4) {videoView.seekTo(videoView.getCurrentPosition() - 5 * 1000);}break;case KeyEvent.KEYCODE_DPAD_RIGHT:  //向右键Log.d(TAG, "right--->");videoView.seekTo(videoView.getCurrentPosition() + 5 * 1000);break;case KeyEvent.KEYCODE_VOLUME_UP:   //调大声音键Log.d(TAG, "voice up--->");break;case KeyEvent.KEYCODE_VOLUME_DOWN: //降低声音键Log.d(TAG, "voice down--->");break;case KeyEvent.KEYCODE_VOLUME_MUTE: //禁用声音Log.d(TAG, "voice mute--->");break;default:break;}return super.onKeyDown(keyCode, event);}

前进后退的控制都这个遥控器监听里面。
MainActivity的完整代码如下:

package com.llw.androidtvdemo;import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;import com.llw.androidtvdemo.view.MyVideoView;import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Formatter;
import java.util.Locale;import butterknife.BindView;
import butterknife.ButterKnife;public class MainActivity extends AppCompatActivity {@BindView(R.id.video_view)MyVideoView videoView;@BindView(R.id.tv_play_time)TextView tvPlayTime;@BindView(R.id.time_seekBar)SeekBar timeSeekBar;@BindView(R.id.tv_total_time)TextView tvTotalTime;@BindView(R.id.lay_finish_bg)RelativeLayout layFinishBg;@BindView(R.id.btn_play_or_pause)ImageButton btnPlayOrPause;@BindView(R.id.btn_restart_play)ImageButton btnRestartPlay;private int key = 0;private Handler handler = new Handler();private Runnable runnable = new Runnable() {public void run() {if (videoView.isPlaying()) {int current = videoView.getCurrentPosition();timeSeekBar.setProgress(current);tvPlayTime.setText(time(videoView.getCurrentPosition()));}handler.postDelayed(runnable, 500);}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);ButterKnife.bind(this);timeSeekBar.setOnSeekBarChangeListener(onSeekBarChangeListener);initVideo();videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {key = 1;btnRestartPlay.setVisibility(View.VISIBLE);layFinishBg.setVisibility(View.VISIBLE);}});videoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {Toast.makeText(MainActivity.this, "播放出错", Toast.LENGTH_SHORT).show();return false;}});}/*** 时间转换方法* @param millionSeconds* @return*/protected String time(long millionSeconds) {SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");Calendar c = Calendar.getInstance();c.setTimeInMillis(millionSeconds);return simpleDateFormat.format(c.getTime());}/*** 初始化VideoView*/private void initVideo() {//本地视频
//        videoView.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/raw/test"));//网络视频final Uri uri = Uri.parse("http://gslb.miaopai.com/stream/ed5HCfnhovu3tyIQAiv60Q__.mp4");videoView.setVideoURI(uri);videoView.requestFocus();videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {int totalTime = videoView.getDuration();//获取视频的总时长tvTotalTime.setText(stringForTime(totalTime));// 开始线程,更新进度条的刻度handler.postDelayed(runnable, 0);timeSeekBar.setMax(videoView.getDuration());//视频加载完成,准备好播放视频的回调videoView.start();}});}/*** 控制视频是  播放还是暂停  或者是重播* @param isPlay* @param keys*/private void isVideoPlay(boolean isPlay, int keys) {switch (keys) {case 0:if (isPlay) {//暂停btnPlayOrPause.setBackground(getResources().getDrawable(R.mipmap.icon_player));btnPlayOrPause.setVisibility(View.VISIBLE);videoView.pause();} else {//继续播放btnPlayOrPause.setBackground(getResources().getDrawable(R.mipmap.icon_pause));btnPlayOrPause.setVisibility(View.VISIBLE);// 开始线程,更新进度条的刻度handler.postDelayed(runnable, 0);videoView.start();timeSeekBar.setMax(videoView.getDuration());timeGone();}break;case 1://重新播放initVideo();btnRestartPlay.setVisibility(View.GONE);layFinishBg.setVisibility(View.GONE);key = 0;break;}}/*** 延时隐藏*/private void timeGone() {new Handler().postDelayed(new Runnable() {@Overridepublic void run() {btnPlayOrPause.setVisibility(View.INVISIBLE);}}, 1500);}/*** 进度条监听*/private SeekBar.OnSeekBarChangeListener onSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {// 当进度条停止修改的时候触发@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {// 取得当前进度条的刻度int progress = seekBar.getProgress();if (videoView.isPlaying()) {// 设置当前播放的位置videoView.seekTo(progress);}}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}@Overridepublic void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {}};//将长度转换为时间StringBuilder mFormatBuilder = new StringBuilder();Formatter mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());private String stringForTime(int timeMs) {int totalSeconds = timeMs / 1000;int seconds = totalSeconds % 60;int minutes = (totalSeconds / 60) % 60;int hours = totalSeconds / 3600;mFormatBuilder.setLength(0);if (hours > 0) {return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();} else {return mFormatter.format("%02d:%02d", minutes, seconds).toString();}}private String TAG = "key";/*** 遥控器按键监听* @param keyCode* @param event* @return*/@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {switch (keyCode) {case KeyEvent.KEYCODE_ENTER:     //确定键entercase KeyEvent.KEYCODE_DPAD_CENTER:Log.d(TAG, "enter--->");//如果是播放中则暂停、如果是暂停则继续播放isVideoPlay(videoView.isPlaying(), key);break;case KeyEvent.KEYCODE_BACK:    //返回键Log.d(TAG,"back--->");return true;   //这里由于break会退出,所以我们自己要处理掉 不返回上一层case KeyEvent.KEYCODE_SETTINGS: //设置键Log.d(TAG, "setting--->");break;case KeyEvent.KEYCODE_DPAD_DOWN:   //向下键/*    实际开发中有时候会触发两次,所以要判断一下按下时触发 ,松开按键时不触发*    exp:KeyEvent.ACTION_UP*/if (event.getAction() == KeyEvent.ACTION_DOWN) {Log.d(TAG, "down--->");}break;case KeyEvent.KEYCODE_DPAD_UP:   //向上键Log.d(TAG, "up--->");break;case KeyEvent.KEYCODE_DPAD_LEFT: //向左键Log.d(TAG, "left--->");if (videoView.getCurrentPosition() > 4) {videoView.seekTo(videoView.getCurrentPosition() - 5 * 1000);}break;case KeyEvent.KEYCODE_DPAD_RIGHT:  //向右键Log.d(TAG, "right--->");videoView.seekTo(videoView.getCurrentPosition() + 5 * 1000);break;case KeyEvent.KEYCODE_VOLUME_UP:   //调大声音键Log.d(TAG, "voice up--->");break;case KeyEvent.KEYCODE_VOLUME_DOWN: //降低声音键Log.d(TAG, "voice down--->");break;case KeyEvent.KEYCODE_VOLUME_MUTE: //禁用声音Log.d(TAG, "voice mute--->");break;default:break;}return super.onKeyDown(keyCode, event);}
}

运行效果如下:
在这里插入图片描述
在这里插入图片描述
播放结束再按确定键就可以重新播放了。

这里再补充一下,在我之前使用的机顶盒很少使用高版本的Android系统,所以没有对高版本的Android系统的https网络访问许可做支持,在Android9.0及以后的版本中,默认是不支持https请求的,这就导致了有一些读者在使用源码是会出现 “ 无法播放此视频 ”,这样的bug,因此我这里补充一下,其实也很简单,在res下新建一个xml文件夹,xml文件夹下新建一个network_security_config.xml文件
在这里插入图片描述
xml文件内容如下:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config><base-config cleartextTrafficPermitted="true" />
</network-security-config>

然后在AndroidManifest.xml的application标签里配置就可以了。
在这里插入图片描述

如有问题请留言,定当第一时间回复您,感谢您的阅读,我是初学者-Study 山高水长,后会有期~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/353952.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

芒果TV会员,月卡最低9.9元,年卡最低128元!

全国首部湘商题材电视剧《一代洪商》&#xff0c;将于3月27日在央视八套&#xff08;电视剧频道&#xff09;播出&#xff0c;芒果TV将线上播出。该剧由王少华编剧&#xff0c;路奇担纲导演&#xff0c;孟凡耀担任总制片人&#xff0c;张丰毅、李立群、张睿、张含韵等人主演&am…

芒果tv官网服务器维护,芒果tv看不了【解决方案】

win7系统有很多人都喜欢使用,我们操作的过程中常常会碰到win7系统芒果tv看不了的问题。如果遇到win7系统芒果tv看不了的问题该怎么办呢&#xff1f;很多电脑水平薄弱的网友不知道win7系统芒果tv看不了究竟该怎么解决&#xff1f;其实不难根据下面的操作步骤就可以解决问题1.DNS…

jquery mini下载_【芒果tv湖南卫视直播】-芒果TV播放器下载v6.3.4 官方正式版

芒果tv湖南卫视直播电脑版最近也改版更新了&#xff0c;在西西在认识中这些媒体一般是不会重视客户端这块的&#xff0c;新版比旧版大了不少&#xff0c;因此软件的版面也是大气了很多&#xff0c;不过对于用户来说只要能及时的观看湖南卫视的节目已经热播的电视剧就OK了哦。芒…

芒果Tv服务器维护,芒果tv怎么看直播?芒果tv直播看不了怎么办?

芒果tv怎么看直播 芒果TV是湖南卫视新媒体金鹰网旗下的网络电视播放器&#xff0c;为用户提供包括电视剧、电影、电视节目、新闻纪实、音乐等多种类型的点播服务。那么芒果tv怎么看直播? 1.打开【芒果TV】&#xff0c;往左划动上方的导航栏。 2.点击【直播】&#xff0c;这里就…

芒果tv视频抓包分析

今天遇到个朋友问我怎么下载芒果tv的蓝光视频&#xff0c;说她也有芒果tv的会员&#xff0c;但是用网上的一些软件下载下来的视频很模糊&#xff0c;根本不是什么蓝光1080p的&#xff0c;所以我们今天就来分析下芒果tv的蓝光视频怎么下载 1.还是老规矩&#xff0c;打开我们的马…

芒果TV 平板端 以EPG(IPTV)形式复刻

技术介绍 使用View-App框架&#xff08;单页面&#xff09;实现交互Web端&#xff0c;使用阿里web播放IPTV端&#xff0c;使用机顶盒播放器使用webpack打包&#xff0c;兼容es5及之前版本功能方面&#xff0c;暂未使用第三方库 项目地址 项目效果&#xff1a;部署地址View-A…

为什么显示芒果tv服务器异常怎么办,芒果tv打不开怎么办 芒果tv打不开解决方法...

大家经常使用芒果tv看电视直播或者热播电视剧&#xff0c;小编也不例外&#xff0c;相信有的朋友遇到过芒果tv打不开的情况。下面小编就为大家分享几个芒果tv打不开的解决方法 原因一&#xff1a;DNS服务器被劫持导致芒果TV直接打不开。 1、如图所示&#xff0c;点击运行——输…

Android开源demo---芒果TV-VIP电影播放器

抓取的是芒果tv官方的api&#xff0c;理由永久可用 补上一张播放图 界面随便写了一下&#xff0c;一共分为三次解析&#xff0c;利用芒果tv的影片ID&#xff0c;最终解析出来的是m3u8的地址&#xff0c;播放器我是用的Vitamio播放器&#xff0c; 贴一下MainActivity的代码 1 pa…

为什么显示芒果tv服务器异常怎么办,芒果TV看不了怎么办?芒果TV打不开怎么办?...

芒果TV是湖南广播电视台推出的一款互联网视频平台&#xff0c;芒果TV能够为用户带来独家的湖南卫视视频内容&#xff0c;如果有一些资源是湖南卫视专有的&#xff0c;那么就可以在芒果TV上看到。近期有网友表示芒果TV看不了&#xff0c;或者称芒果TV打不开&#xff0c;对于这个…

新版芒果tv电脑版 v6.3.9官方版

芒果tv新版是湖南卫视新媒体金鹰网旗下的免费网络直播电脑版客户端&#xff0c;在整合湖南广播电视台和芒果传媒优质资源的基础上&#xff0c;大力整合内容、创新服务应用、拓展传播领域、完善用户体验&#xff0c;开辟新型视频传播业态&#xff0c;贯通视频生活应用&#xff0…

芒果TV视频点播器

2019独角兽企业重金招聘Python工程师标准>>> <!DOCTYPE html> <html> <head> <meta http-equiv"Content-Type" content"text/html;charsetutf-8"> <title>芒果TV视频点播器---eagle天堂盲鹰</title> …

Unity基础框架从0到1(六)对象池模块

索引 这是Unity基础框架从0到1的第六篇文章&#xff0c;框架系列的项目地址是&#xff1a;https://github.com/tang-xiaolong/SimpleGameFramework 文章最后有目前框架系列的思维导图&#xff0c;前面的文章和对应的视频我一起列到这里&#xff1a; 文章 Unity基础框架从0到…

【新版】系统架构设计师 - 新老教材对比分析

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 文章目录 新老教材比较新版教材章节分析 新老教材比较 提示&#xff1a;请自行购买并浏览新版系统架构设计师教材 原教材&#xff1a;2009年出版&#xff0c;共21章&#xff0c;572页。新教材&#xff1a;2022年出…

提高客户满意度的4种方式

随着技术的使用越来越多&#xff0c;客户体验格局已经永远改变了。长时间的等待时间和缓慢的响应不再被接受&#xff0c;并且对客户满意度产生巨大影响。即时满足和满足客户的高期望至关重要。 那么如何提高客户满意度呢&#xff0c;接下来将为您推荐五种最常见的方法&#xf…

leetcode450. 删除二叉搜索树中的节点(java)

删除二叉搜索树中的节点 leetcode450. 删除二叉搜索树中的节点题目描述 解题思路代码演示二叉树专题 leetcode450. 删除二叉搜索树中的节点 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/delete-node-in-a-bst 题目描述…

深度学习实战(十一)——多标签分类(基于Keras)

目的&#xff1a; 训练一个分类器来将物品分到不同的类别中&#xff0c;比如一件衣服&#xff1a;可以安照服饰类别、颜色、质地打上“衬衫”、“蓝色”、“棉”的标签 服饰类别&#xff1a;衬衫、裙子、裤子、鞋类等 颜色&#xff1a;红、蓝、黑等 质地&#xff1a;棉、羊毛、…

Python中实现文本分类(附代码、数据集)

本文将详细介绍文本分类问题并用Python实现这个过程。 引言 文本分类是商业问题中常见的自然语言处理任务&#xff0c;目标是自动将文本文件分到一个或多个已定义好的类别中。文本分类的一些例子如下&#xff1a; 分析社交媒体中的大众情感鉴别垃圾邮件和非垃圾邮件自动标注客户…

CharTextCNN(AG数据集---新闻主题分类)

文章目录 CharTextCNN一、文件目录二、语料集下载地址&#xff08;本文选择AG&#xff09;三、数据处理(data_loader.py)四、模型&#xff08;chartextcnn.py&#xff09;五、训练和测试实验结果 CharTextCNN 一、文件目录 二、语料集下载地址&#xff08;本文选择AG&#xff0…

[NOI2009] 描边

题目描述 小 Z 是一位杰出的数学家。聪明的他特别喜欢研究一些数学小问题。 有一天&#xff0c;他在一张纸上选择了 n 个点&#xff0c;并用铅笔将它们两两连接起来&#xff0c;构成 (&#xfffd;−1)22n(n−1)​ 条线段。由于铅笔很细&#xff0c;可以认为这些线段的宽度为…

【英文文本分类实战】之二——数据集挑选与划分

请参考本系列目录&#xff1a;【英文文本分类实战】之一——实战项目总览 下载本实战项目资源&#xff1a;神经网络实现英文文本分类.zip&#xff08;pytorch&#xff09; [1] 数据集平台 在阅读了大量的论文之后&#xff0c;由于每一篇论文都会提出一个模型&#xff0c;十分想…