hello,大家好,我就是那个会掀桌子的话唠,刚刚结束两篇关于音频播放与录制的文章,旧坑未埋就挖新坑,还望多多关照。最近累趴了,周末果断休假。

</p>

快看,用力戳它:https://github.com/CarGuo/GSYVideoPlayer 。项目是翻改至<a href="https://github.com/lipangit/JieCaoVideoPlayer">JieCaoVideoPlayer</a>,本文特长,看官请耐心,妹子会有的。

效果

</p>

开源播放器选择

</p>

Android上最为人熟知的MediaPlayer,对,就是这货,在上两篇音频文章中频频露脸的家伙,这次又有它的身影,然而还是这次不讲他,就连他的封装类VideoView也不讲<( ̄︶ ̄)>,呸呸呸,又扯了一堆没用的。

  • <a href="https://github.com/Bilibili/ijkplayer">ijkplayer</a>,这次要推荐的是它,鼎鼎大名的BILIBILI开源的播放器。基于FFMPEG,支持Android与IOS,还封装了谷歌亲儿子MediaPlayer与干儿子<a href="https://github.com/google/ExoPlayer">EXOPlayer</a>(为什么要用EXO),支持直播流,Star-9000多与fork-3000的视频播放器你支持安利。(issues 600多算活跃吗┑( ̄Д  ̄)┍)

集成工作还是有定的工作量的,它的DEMO肯定满足不了欲求不满的设计狮和产品汪的,这里我们不跑分,不打广告,不讲原理,只求站在巨人的肩膀上学(cao)习(xi),快速集成。

  • 定义一个单例的视频内核播放管理器。
  • 自定义一个满足你上下其手的TextureView
  • 定义一个UI层级逻辑播放器
  • 重力旋转的相关逻辑处理
  • 列表逻辑的相关处理
  • 列表到全屏相关的逻辑处理
  • 视频缓存逻辑

1、播放管理器:GSYVideoManager

单例,没得商量,它需要负责真正的播放请求与显示逻辑,集成了IjkMediaPlayer,BILIBLI的开源小组还是很有心的,它的封装和接口使用基本和MediaPlayer没有什么区别,只需要用起来就好了。‘

这里我们要实现IjkMediaPlayer的播放接口,监听IjkMediaPlayer的相关状态回调然后封发到各个逻辑播放器中。从下方代码可以看到,真的和MediaPlayer好像。

mediaPlayer = new IjkMediaPlayer();
//音频类型
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
//数据源
mediaPlayer.setDataSource(((GSYModel) msg.obj).getUrl(), ((GSYModel) msg.obj).getMapHeadData());
//播放完成
mediaPlayer.setOnCompletionListener(GSYVideoManager.this);
//缓冲
mediaPlayer.setOnBufferingUpdateListener(GSYVideoManager.this);
//常亮
mediaPlayer.setScreenOnWhilePlaying(true);
//加载完毕
mediaPlayer.setOnPreparedListener(GSYVideoManager.this);
//拖动
mediaPlayer.setOnSeekCompleteListener(GSYVideoManager.this);
//失败
mediaPlayer.setOnErrorListener(GSYVideoManager.this);
//视频相关信息-重要
mediaPlayer.setOnInfoListener(GSYVideoManager.this);
//视频大小
mediaPlayer.setOnVideoSizeChangedListener(GSYVideoManager.this);】
//开始加载
mediaPlayer.prepareAsync();

监听的回调接口里,大部分大家都耳目能详吧,没听过也没关系,都写上就对了,但是最主要需要关注的两个,一个是通过setOnVideoSizeChangedListener拿到视频宽和高,这是我们后续正常显示视频的依靠之一。

另外一个就是setOnInfoListener,这里我们主要是获取到视频相关的元信息里视频旋转角度!还记得那时候对视频播放不熟悉,和产品还有QA力争“这个视频本来就是转了90度的,我就不改,你咬我吗···”这样的黑历史。Σ( ° △ °|||)

特别是Android拍摄的竖屏视频,旋转不是视频本身的图像,而是增加了旋转信息,而这个时候你需要做的就是识别它,然后转了它丫的。另外,因为Android本身的MediaPlaer和VideoView自身就处理好所以不需要你旋转。((ノO益O)ノ彡┻━┻亲生的啊)

这里的接口主要是把当前播放的视频状态和信息到返回到逻辑播放器中。

@Override
public void onInfo(int what, int extra) {if (what == MediaPlayer.MEDIA_INFO_BUFFERING_START) {BACKUP_PLAYING_BUFFERING_STATE = mCurrentState;setStateAndUi(CURRENT_STATE_PLAYING_BUFFERING_START);} else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END) {if (BACKUP_PLAYING_BUFFERING_STATE != -1) {setStateAndUi(BACKUP_PLAYING_BUFFERING_STATE);BACKUP_PLAYING_BUFFERING_STATE = -1;}} else if (what == IMediaPlayer.MEDIA_INFO_VIDEO_ROTATION_CHANGED) {//这里返回了视频旋转的角度,根据角度旋转视频到正确的画面mRotate = extra;if (mTextureView != null)mTextureView.setRotation(mRotate);}
}

2、自定义TextureView:GSYTextureView

为什么不用SurfaceView?因为TextureView很可爱啊。这里我们主要针对视频的大小和旋转角度设置TextureView的大小,详细就不多说了(不是懒),挑其中一类讲讲,因为主要也是这个。

  • 例如根据视频的长宽比和屏幕的长宽比判断,如果视频宽与屏幕宽之比小于高之比,那么就需要按理比压缩宽度,然后高度适应屏幕。
  • 例如根据旋转信息,判断TextureView界面的比例是横的还是竖的,如果View是竖的,而视频也是竖的,那么因为旋转了90度,那么让视频的高显示为屏幕的宽度,从新计算旋转后的宽度。

觉得看起来有点绕口?没关系,用着用着就习惯了····

width = widthSpecSize;
height = heightSpecSize;
···
if (videoWidth * height < width * videoHeight) {width = height * videoWidth / videoHeight;
} else if (videoWidth * height > width * videoHeight) {height = width * videoHeight / videoWidth;
}
···
if (getRotation() != 0 && getRotation() % 90 == 0) {if (widthS < heightS) {if (width > height) {width = (int) (width * (float) widthS / height);height = widthS;} else {height = (int) (height * (float) width / widthS);width = widthS;}} else {if (width > height) {height = (int) (height * (float) width / widthS);width = widthS;} else {width = (int) (width * (float) widthS / height);height = widthS;}}
}

3、UI层级逻辑播放器 GSYVideoPlayer

所有的UI逻辑基本都可以写到这里,目前继承了 FrameLayout,View.OnClickListener, View.OnTouchListener, SeekBar.OnSeekBarChangeListener, TextureView.SurfaceTextureListener和GSYMediaPlayerListener。
 
 逻辑播放器实现的内容太多了,这里主要说几个地方,好吧,我承认我懒╮(╯_╰)╭ ,但是写太多了也没人看啊,所以这里主要是说一些关键的点,有需要留言再开个坑聊一聊,反正有DEMO。

  • 记录界面的播放状态,把播放管理器GSYVideoManager的状态记录下来,如果有别的逻辑播放器点击播放了,就把原本的逻辑播放器状态清空,所有逻辑播放器的整个界面的UI都是根据这个State来决定的。

在逻辑播放器中统一分发各种状态,把被播放的manager状态同步到这里,之后你想要在哪个逻辑播放器里播放只需要对应的设置状态后把manager的监听同步过来。

switch (mCurrentState) {//正常初始化状态case CURRENT_STATE_NORMAL:if (isCurrentMediaListener()) {cancelProgressTimer();GSYVideoManager.instance().releaseMediaPlayer();}break;//loading中case CURRENT_STATE_PREPAREING:resetProgressAndTime();break;//播放中case CURRENT_STATE_PLAYING:startProgressTimer();break;//暂停case CURRENT_STATE_PAUSE:startProgressTimer();break;//错误-需要判断是否切换了逻辑播放器case CURRENT_STATE_ERROR:if (isCurrentMediaListener()) {GSYVideoManager.instance().releaseMediaPlayer();}break;//结束case CURRENT_STATE_AUTO_COMPLETE:cancelProgressTimer();mProgressBar.setProgress(100);mCurrentTimeTextView.setText(mTotalTimeTextView.getText());break;
}
  • 增加界面的onTouch事件,根据ViewgetId判断触摸的是进度条还是界面,如果是界面判断是左右滑动就显示DialogseekTo,如果是上下就根据屏幕的左边还是右边来选择是调节音量还是亮度
···
case MotionEvent.ACTION_MOVE:float deltaX = x - mDownX;float deltaY = y - mDownY;float absDeltaX = Math.abs(deltaX);float absDeltaY = Math.abs(deltaY);//是全屏还是设置了可以触摸if (mIfCurrentIsFullscreen || mIsTouchWiget) {//之前是否已经符合了触摸逻辑条件if (!mChangePosition && !mChangeVolume && !mBrightness) {//如果手指动了超过一定距离就可以判断是滑动,防止点击的误判的if (absDeltaX > mThreshold || absDeltaY > mThreshold) {cancelProgressTimer();//如果是左右的就是进度if (absDeltaX >= mThreshold) {mChangePosition = true;mDownPosition = getCurrentPositionWhenPlaying();if (mVideoAllCallBack != null && isCurrentMediaListener()) {mVideoAllCallBack.onTouchScreenSeekPosition(mUrl, mObjects);}} else {//如果是上下的判断是左边还是右边if (mFirstTouch) {mBrightness = mDownX < mScreenWidth * 0.5f;mFirstTouch = false;}if (!mBrightness) {mChangeVolume = true;mGestureDownVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);if (mVideoAllCallBack != null && isCurrentMediaListener()) {mVideoAllCallBack.onTouchScreenSeekVolume(mUrl, mObjects);}}}}}}···//根据flag执行逻辑
  • 要监听TextureView.setSurfaceTextureListener来通知画面的创建和销毁,比如回到后台,onPause等。

这里有一个是TextureView的动态添加,动态添加的好处是你可以在不停止视频的情况下载不同的逻辑播放器中切换视频播放,比如列表全屏。

protected void addTextureView() {if (mTextureViewContainer.getChildCount() > 0) {mTextureViewContainer.removeAllViews();}mTextureView = null;mTextureView = new GSYTextureView(getContext());mTextureView.setSurfaceTextureListener(this);mTextureView.setRotation(mRotate);RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);mTextureViewContainer.addView(mTextureView, layoutParams);
}···//把Surface丢给视频播放管理
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {mSurface = new Surface(surface);GSYVideoManager.instance().setDisplay(mSurface);
}//告诉视频播放渲染画面销毁了
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {GSYVideoManager.instance().setDisplay(null);surface.release();return true;
}
  • 每次播放都要把Manager的player的监听移到当前播放的逻辑播放器,这样才能够正确的监听视频的播放状态。
//这里其实就有播放管理器的监听分发保存的逻辑需要注意
GSYVideoManager.instance().setLastListener(this);
GSYVideoManager.instance().setListener(gsyVideoPlayer);

3、列表全屏逻辑 :Window层级的全屏、单例逻辑播放器的全屏ListVideoUtil。

</p>
效果GIF(比较大):

1)、Window层级的

传闻每一个Activity都有一个com.android.internal.R.id.content,它默默的包含了各种你塞进去的物体,而且是一个FrameLayout,谷歌有太多它的传说了,我们用它是就是。

既然是FrameLayout,那么我们往他里面塞东西就好了,这里我们可以在GSYVideoPlayer里面写一个方法,在点击全屏按钮的时候:

  • 隐藏状态栏,清除当前TextureView。
  • 然后新创建一个GSYVideoPlayer2,只有把这个G2添加到window下FrameLayout
  • 设置它的播放状态和当前列表这个逻辑播放器一致。
  • 最后把G2告知Manager承接画面,这样是就实现了无缝的列表到全屏啦,返回只需要倒着做就好了。

在切换的时候可以做一些位移动画,让播放器的全屏更加友好,下面长代码来袭((/- -)/。深夜码字不易,不知道为什么每次这个时候老婆的意见很大啊。

Constructor<GSYBaseVideoPlayer> constructor = (Constructor<GSYBaseVideoPlayer>) GSYBaseVideoPlayer.this.getClass().getConstructor(Context.class);
final GSYBaseVideoPlayer gsyVideoPlayer = constructor.newInstance(getContext());
//记录新创建的这个video的id,在返回的时候通过它销毁
gsyVideoPlayer.setId(FULLSCREEN_ID);
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
final int w = wm.getDefaultDisplay().getWidth();
final int h = wm.getDefaultDisplay().getHeight();
//设置黑色背景,自动充满全屏
FrameLayout.LayoutParams lpParent = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
FrameLayout frameLayout = new FrameLayout(context);
frameLayout.setBackgroundColor(Color.BLACK);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//如果5.0的话,先让播放器出现的位置和列表中一直,再样式一会执行到屏幕中间的过度动画效果FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(getWidth(), getHeight());lp.setMargins(mListItemRect[0], mListItemRect[1], 0, 0);frameLayout.addView(gsyVideoPlayer, lp);vp.addView(frameLayout, lpParent);mHandler.postDelayed(new Runnable() {@Overridepublic void run() {TransitionManager.beginDelayedTransition(vp);resolveFullVideoShow(context, gsyVideoPlayer, h, w);}}, 300);
} else {//5.0一下直接显示FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(getWidth(), getHeight());frameLayout.addView(gsyVideoPlayer, lp);vp.addView(frameLayout, lpParent);resolveFullVideoShow(context, gsyVideoPlayer, h, w);
}
//设置全屏逻辑播放器的状态,动态及添加播放view
gsyVideoPlayer.setUp(mUrl, mCache, mObjects);
gsyVideoPlayer.setStateAndUi(mCurrentState);
gsyVideoPlayer.addTextureView();
//添加监听
GSYVideoManager.instance().setLastListener(this);
GSYVideoManager.instance().setListener(gsyVideoPlayer);
2)、ListVideoUtil的单例模式

这里利用另外一种实现思路,列表的逻辑播放器只用一个,因为普通的list在滑动的时候会有复用和销毁,这会导致视频被释放而停止了,如果你是和今日黄(tou)条一样的视频列表播放效果,滑出屏幕就停止那无所谓。

如果你需要无论怎么滑动,视频都在原来的位置播放的话,那么ListVideoUtil适合你,,内部它已经带了全屏,防错位,旋转的各种逻辑,直接上代码,有兴趣的看DEMO。


listVideoUtil = new ListVideoUtil(this);
//设置列表最外层的布局用于全屏,空FrameLayout
listVideoUtil.setFullViewContainer(videoFullContainer);
//全屏隐藏状态栏,如果有的话
listVideoUtil.setHideStatusBar(true);···
//在列表中吧列表位置,封面,哪个列表的TAG,列表视频的承载ViewGroup,播放按键传入到Utils中
listVideoUtil.addVideoPlayer(position, imageView, TAG, holder.videoContainer, holder.playerBtn);
holder.playerBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//每次播放都要更新列表让其他的item恢复状态notifyDataSetChanged();//设置播放的tag和位置,防止错位listVideoUtil.setPlayPositionAndTag(position, TAG);//开始播放final String url = "http://baobab.wdjcdn.com/14564977406580.mp4";listVideoUtil.startPlay(url);}
});

4、OrientationUtils 重力旋转的工具类

OrientationUtils使用的是OrientationEventListener,通过手机的角度判断需要旋转到哪个位置。为什么用它?因为谷歌到的时候刚好看到,缘分啊懂吗。

这里需要个关注的是手动点击和自动旋转之间的冲突,主要看代码吧,老婆开始催我了 (ノಠ益ಠ)ノ彡┻━┻。

//判断系统是否开了旋转,是的,这货不需要系统旋转是否开启
boolean autoRotateOn = (android.provider.Settings.System.getInt(activity.getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0) == 1);if (!autoRotateOn) {if (mIsLand == 0) {return;}}// 设置竖屏if (((rotation >= 0) && (rotation <= 30)) || (rotation >= 330)) {//是否点击导致的if (mClick) {if (mIsLand > 0 && !mClickLand) {return;} else {//清除状态mClickPort = true;mClick = false;mIsLand = 0;}} else {//自动旋转if (mIsLand > 0) {screenType = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);gsyVideoPlayer.getFullscreenButton().setImageResource(R.drawable.video_enlarge);mIsLand = 0;mClick = false;}}}// 设置横屏else if (((rotation >= 230) && (rotation <= 310))) {if (mClick) {if (!(mIsLand == 1) && !mClickPort) {return;} else {mClickLand = true;mClick = false;mIsLand = 1;}} else {if (!(mIsLand == 1)) {screenType = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);gsyVideoPlayer.getFullscreenButton().setImageResource(R.drawable.video_shrink);mIsLand = 1;mClick = false;}}}// 设置反向横屏else if (rotation > 30 && rotation < 95) {if (mClick) {if (!(mIsLand == 2) && !mClickPort) {return;} else {mClickLand = true;mClick = false;mIsLand = 2;}} else if (!(mIsLand == 2)) {screenType = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);gsyVideoPlayer.getFullscreenButton().setImageResource(R.drawable.video_shrink);mIsLand = 2;mClick = false;}}}
};
orientationEventListener.enable();

6、边播边缓存

好吧,老婆睡了,我偷偷起来了(。・・)ノ
 这个需求曾经让我彻夜难眠,因为IJKPlayer不支持,好吧,没见过哪个播放器支持的,和产品争(tuo)论(yan)需(shi)求(jian)之后,最终还是github大法好:<a href="https://github.com/danikula/AndroidVideoCache">AndroidVideoCache</a>。

接入简单,使用简单,你可以趾高气扬的和产品说,这个so easy了。

HttpProxyCacheServer proxy = getProxy();
//注意不能传入本地路径,本地的你还传进来干嘛。
String proxyUrl = proxy.getProxyUrl(VIDEO_URL);
videoView.setVideoPath(proxyUrl);

该项目的原理其实就是将url链接转化为本地链接 h t t p://127.0.0.1:LocalPort/url,然后它开一个服务器一边下载缓存视频,一边把缓存的数据正常返回给你的播放器,如果已经缓存过的这里会返回一个本地文件路径。Σ( ° △ °|||)︴曾经的我真的是too young too smiple。

5、一些坑和说明

  • 1、IJKPLAY的后台播放和回到前台恢复画面的速度之快是其他播放器(我坐井观天)无法比拟的,真的好快,而且适合你,因为你什么都不用做。

  • 2、IJKPLAY有一个问题,我也提过ISSUSE了 <a href="https://github.com/Bilibili/ijkplayer/issues/2104">#2104</a>,不过目前还未解决,就是某些短小的视频会无法seekTo,说是FFMEPG的问题,然后就太监了。

  • 3、IJKPLAY库里还封装了exoplayer谷歌干儿子,用法也基本一致,这个播放器自己内部判断旋转,不会有上面的seekto问题,可是后台或者onPause之后的画面恢复速度堪忧啊,各位遇到过吗?

  • 4、千万别开硬解码,不然会这样。 ( ‵o′)凸

  • 5、拖动进度条,需要在停止拖动的时候,判断视频是不是已经播放完了被释放了。

  • 6、如果横屏全屏的话,恢复到正常画面是最好有一个延时,这样画面才不会出现背景抖动的问题,还有最关键的,Maifest文件。

//不要忘记配置activity,所有背景的activityandroid:configChanges="orientation|keyboardHidden|screenSize"
  • 7、普通列表中播放视频在快速移动可能出现的错位问题
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {int lastVisibleItem = firstVisibleItem + visibleItemCount;//大于0说明有播放if (GSYVideoManager.instance().getPlayPosition() >= 0) {//当前播放的位置int position = GSYVideoManager.instance().getPlayPosition();//对应的播放列表TAGif (GSYVideoManager.instance().getPlayTag().equals(ListNormalAdapter.TAG)&& (position < firstVisibleItem || position > lastVisibleItem)) {//如果滑出去了上面和下面就是否,和今日头条一样GSYVideoPlayer.releaseAllVideos();listNormalAdapter.notifyDataSetChanged();}}
}

到底了呢(o)/。

下面的的看到了吗 ?<( ̄︶ ̄)>

点我点我上60级:https://github.com/CarGuo/GSYVideoPlayer

</p>
能看到这里都是真爱啊,我最后问两句,你们会觉得文章太长阅读起来比较费劲吗?

相关文章: Android 列表视频的全屏、自动小窗口优化实践
 
友情链接:

  • <a href="https://github.com/CarGuo/GSYVideoPlayer">GSYVideoPlayer</a>

  • <a href="https://github.com/Bilibili/ijkplayer">ijkplayer</a>

  • <a href="https://github.com/danikula/AndroidVideoCache">AndroidVideoCache</a>

  • <a href="https://github.com/lipangit/JieCaoVideoPlayer">JieCaoVideoPlayer</a>


作者:恋猫月亮链接:https://www.jianshu.com/p/9fe377dd9750來源:简书著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Android 实现视屏播放器、边播边缓存功能、外加铲屎(IJKPlayer)相关推荐

  1. Android 实现视屏播放器、边播边缓存功能、外加铲屎(IJKPlayer)(转载)

    转载自:<Android 实现视屏播放器.边播边缓存功能.外加铲屎(IJKPlayer)> hello,大家好,我就是那个会掀桌子的话唠,刚刚结束两篇关于音频播放与录制的文章,旧坑未埋就挖 ...

  2. Android 实现视屏播放器、边播边缓存功能,附源码

    热文导读 | 点击标题阅读 [墙裂推荐]AndroidVideoCache:实现视屏播放边下边播 吊炸天!74款APP完整源码! 一份年薪30万的Android面试宝典,附答案 来源:http://w ...

  3. android studio 视屏播放器 MediaController

    url来自raw文件中,自己放进去的Mp4: 视屏可以拖动进度条: XML: <?xml version="1.0" encoding="utf-8"?& ...

  4. 使用java创建一个简易的视屏播放器

    最近有个多媒体的作业,要求使用visualC++和opencv编写一个简易的视屏播放器,对于C/C++残疾者而言是不可能的,于是萌生了用java编写的想法.具体经验分享一下. 目标:制作简易视屏播放器 ...

  5. java 生成media_使用java创建一个简易的视屏播放器

    最近有个多媒体的作业,要求使用visualC++和opencv编写一个简易的视屏播放器,对于C/C++残疾者而言是不可能的,于是萌生了用java编写的想法.具体经验分享一下. 目标:制作简易视屏播放器 ...

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

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

  7. 使用AVPlaer创建视屏播放器

    //#PlayerView.m文件中 #import "PlayerView.h" #import <AVFoundation/AVFoundation.h> #imp ...

  8. video 视屏播放器详细控制

    介绍 canplay.play.pause是video提供的API,在视频加载完成后需要设置视频的总时长duration也是来源于自身的API但是需要格式化时间 设置当前播放时间通过自身API,cur ...

  9. android 网易视频无法播放器,没错,这就是目前功能最强第三方播放器

    我们先前安利的很多视频播放器,因为是个人开发者作品,通常不会要求太多,只要能用够用没广告就很满足了. 但用久了大家可能会感觉不那么舒服,比如软件视觉体验,操作体验跟专业的播放器差距甚远,或者仅仅是简介 ...

最新文章

  1. Flink并行度与Slot的关系
  2. Confluence 6 数据库结构图
  3. pythonpandas用sql查询数据表_pandas从数据库读取数据
  4. 循环获取枚举值和名称
  5. oracle集群rac无法访问,解决Oracle 11g R2 RAC 无法在客户端通过scanIP连接数据库
  6. CompletableFuture详解~thenApplyAsync
  7. 解决centos sudo执行仍旧显示Permission denied
  8. Android开发笔记(七十七)图片缓存算法
  9. ruby语言仅仅是昙花一现
  10. 计算机骗局案例,骗局揭秘 | 卖你一台假电脑,再送你一个假鲁大师
  11. java里oop思想_(一)OOP思想详解
  12. 框架合集:Java框架自学视频教程-动力节点
  13. 奈奎斯特曲线怎么确定w的值matlab,用MATLAB绘制Nyquist图.ppt
  14. 计蒜客--T1212 仙岛求药
  15. 写项目总结写哪些方面
  16. python爬取企业电话_Python爬取天眼查企业数据
  17. 明源云客微信抢房技巧_微信抢房软件开发 - heartdong - OSCHINA - 中文开源技术交流社区...
  18. 打印1000-2000年的闰年
  19. linux tuxedo查看服务进程数,tuxedo管理命令之tmboot与tmshutdown
  20. Android隐藏和显示输入法

热门文章

  1. 微信支付appid参数不正确
  2. 走进GBase 8s之安全功能(二)安全标记与强制访问控制
  3. macOS 的「预览」有几种用法
  4. 微信小程序input组件边框不显示问题的解决方法
  5. react-native热更新之code-push
  6. Oracle数值函数,日期函数,转换函数,通用函数(七)
  7. python报错1-'int' object is not iterable('int'对象不可迭代)
  8. python生成一笔画_一笔画小游戏-算法-python+autojs
  9. 流镜像(华为S9306和S5700)
  10. 通达信自动交易软件 z