theme: channing-cyan

一.初始化分析

1.初始化主要是构造了一个StandardGSYVideoPlayer

2.设置全屏布局的容器

代码如下:

listVideoUtil = new ListVideoUtil(this);
listVideoUtil.setFullViewContainer(videoFullContainer);
​
​
​
public ListVideoUtil(Context context) {gsyVideoPlayer = new StandardGSYVideoPlayer(context);this.context = context;
}

StandardGSYVideoPlayer的初始化化主要是加载R.layout.video_layout_standard这个布局,并且实例化一些组件,为组件设置监听事件;

初始化组件包括:播放/暂停按钮,播放时长,总时长,播放进度条,返回按钮,全屏按钮和视频标题,

初始化组件不包括:用于显示视频的TextureView,这个TextureView是动态添加的,放在下片文章中讲述;

初始化内容比较简单;

思考以下几个问题:

1.视频窗口的大小和位置是如何匹配ListView的item大小和位置的?

2.视频播放画面是如何显示出来的?

3.视频播放的声音如何显示出来的?

通过代码分析;

 @Overridepublic View getView(final int position, View convertView, ViewGroup parent) {
​....
​....
​listVideoUtil.addVideoPlayer(position, holder.imageView, TAG, holder.videoContainer, holder.playerBtn);
​holder.playerBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {notifyDataSetChanged();//listVideoUtil.setLoop(true);listVideoUtil.setPlayPositionAndTag(position, TAG);
//        final String url = "https://tv.miguvideo.com/?from=singlemessage&isappinstalled=0#video/live/761358370/room201706301600304971_R1";//listVideoUtil.setCachePath(new File(FileUtils.getPath()));final String url = "http://baobab.wdjcdn.com/14564977406580.mp4";listVideoUtil.startPlay(url);}});
​
•    ........
​.......
​
}
​
listview的adapter的getview方法每次都会走一次listVideoUtil.addVideoPlayer(position, holder.imageView, TAG, holder.videoContainer, holder.playerBtn);
/*** 动态添加视频播放** @param position 位置* @param imgView  封面* @param tag    TAG类型* @param container player的容器* @param playBtn  播放按键*/public void addVideoPlayer(final int position, View imgView, String tag,ViewGroup container, View playBtn) {container.removeAllViews();if (isCurrentViewPlaying(position, tag)) {if (!isFull) {ViewGroup viewGroup = (ViewGroup) gsyVideoPlayer.getParent();if (viewGroup != null)viewGroup.removeAllViews();container.addView(gsyVideoPlayer);playBtn.setVisibility(View.INVISIBLE);}} else {playBtn.setVisibility(View.VISIBLE);container.removeAllViews();  //增加封面container.addView(imgView);}}

代码可以看出addVideoPlayer方法的意思是,如果当前的position等于listVideoUtil中获取到的position,videoContainer中就显示gsyVideoPlayer,否则显示视频封面;

点击播放按钮的作用:

1.向listVideoUtil中设置position,并通过notifyDataSetChanged方法使重新走getview(),重新走getview方法时,会重新进入addVideoPlayer方法判断;

至此第一个疑问已经解决:视频显示在哪个item就是这个position决定的;视频窗口的大小由videoContainer的大小决定;

2.执行代码listVideoUtil.startPlay(url);

下面开始分析startPlay(url)中做了哪些操作?

 /*** 开始播放** @param url 播放的URL*/public void startPlay(String url) {
​
•    if (isSmall()) {
•      smallVideoToNormal();//如果是小窗口,就转为正常窗口播放,这个问题后面再说
•    }
​
•    this.url = url;
​
•    gsyVideoPlayer.release();
​
•    gsyVideoPlayer.setLooping(isLoop);//视频是否循环播放
​
•    gsyVideoPlayer.setSpeed(speed);//视频播放速度
​
•    gsyVideoPlayer.setNeedShowWifiTip(needShowWifiTip);//非wifi环境下,显示流量提醒
​
•    gsyVideoPlayer.setNeedLockFull(needLockFull);//是否需要全屏锁屏
​
•    gsyVideoPlayer.setUp(url, true, cachePath, mapHeadData, objects);//设置边缓存边播放
​
•    //增加title
•    gsyVideoPlayer.getTitleTextView().setVisibility(View.GONE);
​
•    //设置返回键
•    gsyVideoPlayer.getBackButton().setVisibility(View.GONE);
​
•    //设置全屏按键功能
•    gsyVideoPlayer.getFullscreenButton().setOnClickListener(new View.OnClickListener() {
•      @Override
•      public void onClick(View v) {
•        resolveFullBtn();
•      }
•    });
​
•    gsyVideoPlayer.startPlayLogic();}
​

就是一些简单设置项,其中gsyVideoPlayer.setUp(url, true, cachePath, mapHeadData, objects);会把url改为全局的mUrl;

​@Overridepublic void startPlayLogic() {if (mStandardVideoAllCallBack != null) {Debuger.printfLog("onClickStartThumb");mStandardVideoAllCallBack.onClickStartThumb(mUrl, mObjects);}prepareVideo();startDismissControlViewTimer();}
   /*** 开始状态视频播放*/protected void prepareVideo() {if (GSYVideoManager.instance().listener() != null) {GSYVideoManager.instance().listener().onCompletion();}GSYVideoManager.instance().setListener(this);GSYVideoManager.instance().setPlayTag(mPlayTag);GSYVideoManager.instance().setPlayPosition(mPlayPosition);addTextureView();//动态添加TextureViewmAudioManager.requestAudioFocus(onAudioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);//暂时获取音频焦点((Activity) getContext()).getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);//保持屏幕常亮不灭屏GSYVideoManager.instance().prepare(mUrl, mMapHeadData, mLooping, mSpeed);setStateAndUi(CURRENT_STATE_PREPAREING);}

科普1,音频焦点分类:

AudioManager.AUDIOFOCUS_REQUEST_GRANTED------------永久获取音频焦点

AudioManager.AUDIOFOCUS_GAIN_TRANSIENT-------------暂时获取音频焦点,比如音乐后台播放,当前视频播放会抢夺音频焦点,视频播放完成,音乐自动播放;

AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK--------提示类型获取音频焦点,比如听音乐的时候来短信,音乐声音降低

​/*** 添加播放的view*/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);}

构造了一个TextureView,并添加到gsyVideoPlayer布局中。

TextureView设置了SurfaceTextureListener;

科普2:SurfaceTextureListener回调方法:

 1.onSurfaceTextureAvailable(SurfaceTexture arg0, int arg1, int arg2)-------------TextureView可用时调用
​2.onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)
​3.onSurfaceTextureDestroyed(SurfaceTexture surface)------------TextureView销毁时调用
​4.onSurfaceTextureUpdated(SurfaceTexture surface)

回到代码:

  @Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {mSurface = new Surface(surface);GSYVideoManager.instance().setDisplay(mSurface);//显示暂停切换显示的图片showPauseCover();}

意思是:在TextureView可以使用时,将surface交给GSYVideoManager,用于显示视频的画面,至此第二个问题已经解决,当然此时还没有开始播放视频, 真正播放视频是在

GSYVideoManager.instance().prepare(mUrl, mMapHeadData, mLooping, mSpeed)中,此处的mUrl就是上面保存的url;

public void prepare(final String url, final Map<String, String> mapHeadData, boolean loop, float speed) {if (TextUtils.isEmpty(url)) return;Message msg = new Message();msg.what = HANDLER_PREPARE;GSYModel fb = new GSYModel(url, mapHeadData, loop, speed);msg.obj = fb;mMediaHandler.sendMessage(msg);
}
public class MediaHandler extends Handler {public MediaHandler(Looper looper) {super(looper);}
​@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);switch (msg.what) {case HANDLER_PREPARE:initVideo(msg);break;
private void initVideo(Message msg) {try {currentVideoWidth = 0;currentVideoHeight = 0;mediaPlayer.release();
​if (videoType == GSYVideoType.IJKPLAYER) {initIJKPlayer(msg);} else if (videoType == GSYVideoType.IJKEXOPLAYER) {initEXOPlayer(msg);}setNeedMute(needMute);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();
​} catch (Exception e) {e.printStackTrace();}
}

根据videoType的类型,创建不同的播放器,initIJKPlayer(msg)或者initEXOPlayer(msg);

不管哪种播放器,msg中有视频播放的url,用于播放器播放视频使用;

最后调用mediaPlayer.prepareAsync()播放视频;

首先思考以下几个问题:

1.点击全屏按钮,视频是怎么填充整个屏幕的?(以及视频放大时的动画效果实现)

2.视频填充屏幕后,又是如何实现横屏的?

3.视频是如何做到,列表中的视频和放大后的视频,无缝衔接的?

依旧通过代码分析:

//设置全屏按键功能
gsyVideoPlayer.getFullscreenButton().setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {resolveFullBtn();}
});

全屏按钮由一个点击事件触发

public void resolveFullBtn() {if (fullViewContainer == null) {return;}if (!isFull) {resolveToFull();} else {resolveMaterialToNormal(gsyVideoPlayer);}
}

fullViewContain是在ListVideoUtil初始化的时候设置的,具体分析可以查看http://blog.csdn.net/qq_15631341/article/details/74332254

代码的意思是:

如果当前不是全屏,走resolveToFull;

如果当前是全屏,走resolveMaterialToNormal;

我们先分析展示全屏的过程,

/*** 处理全屏逻辑*/
private void resolveToFull() {systemUiVisibility = ((Activity) context).getWindow().getDecorView().getSystemUiVisibility();CommonUtil.hideSupportActionBar(context, hideActionBar, hideStatusBar);//根据需求隐藏actionbar和statusbarif (hideKey) {hideNavKey(context);}isFull = true;//将gsyVideoplayer从原来的容器中剥离,即gsyVideoPlayer不在显示在ListView的摸一个item上了ViewGroup viewGroup = (ViewGroup) gsyVideoPlayer.getParent();//此处需要注意,gsyvideo还没有从listview的item上移除时,保存了当前的布局信息,用于后面动画的展示
listParams = gsyVideoPlayer.getLayoutParams(); if (viewGroup != null) { listParent = viewGroup; viewGroup.removeView(gsyVideoPlayer); }gsyVideoPlayer.setIfCurrentIsFullscreen(true); gsyVideoPlayer.getFullscreenButton().setImageResource(gsyVideoPlayer.getShrinkImageRes());//替换全屏按钮的图片为退出全屏的图片 gsyVideoPlayer.getBackButton().setVisibility(View.*VISIBLE*); //初始化旋转工具 orientationUtils = new OrientationUtils((Activity) context, gsyVideoPlayer); orientationUtils.setEnable(isAutoRotation()); gsyVideoPlayer.getBackButton().setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { resolveMaterialToNormal(gsyVideoPlayer);//设置退出全屏监听 } }); if (showFullAnimation) { if (fullViewContainer instanceof FrameLayout) {//目前只做了frameLoayout的判断 resolveMaterialAnimation(); } else { resolveFullAdd(); } } else { resolveFullAdd(); }} 如果设置的需要显示动画且当前的容器是FrameLayout就执行resolveMaterialAnimation();

否则执行resolveFullAdd()方法;即这两种方法的区别是一个是有动画效果的,一个是无动画效果的;

先来看一下没有动画效果的是如何实现的?

/*** 添加到全屏父布局里*/
private void resolveFullAdd() {fullViewContainer.setBackgroundColor(Color.BLACK);fullViewContainer.addView(gsyVideoPlayer);resolveChangeFirstLogic(50);
}

就是把一开始设置的fullViewContain的背景改为黑色,再把gsyVideoPlayer的布局添加进来,最后执行resolveChangeFirstLogic(50)方法;

再来看一下有动画效果的是 如何实现的?

/*** 如果是5.0的动画开始位置*/
private void resolveMaterialAnimation() {listItemRect = new int[2];listItemSize = new int[2];saveLocationStatus(context, hideStatusBar, hideActionBar);//保存gsyvideoplayer在listview的item中的位置和大小FrameLayout.LayoutParams lpParent = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);FrameLayout frameLayout = new FrameLayout(context);frameLayout.setBackgroundColor(Color.BLACK);FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(listItemSize[0], listItemSize[1]);lp.setMargins(listItemRect[0], listItemRect[1], 0, 0);frameLayout.addView(gsyVideoPlayer, lp);fullViewContainer.addView(frameLayout, lpParent);handler.postDelayed(new Runnable() {@Overridepublic void run() {//开始动画TransitionManager.beginDelayedTransition(fullViewContainer);resolveMaterialFullVideoShow(gsyVideoPlayer);resolveChangeFirstLogic(600);}}, 300);
}
/*** 保存大小和状态*/
private void saveLocationStatus(Context context, boolean statusBar, boolean actionBar) {listParent.getLocationOnScreen(listItemRect);//获取listView中的gsyvideoplayer相对于屏幕的左边距和上边距并存入listItemRect中int statusBarH = getStatusBarHeight(context);int actionBerH = getActionBarHeight((Activity) context);if (statusBar) {listItemRect[1] = listItemRect[1] - statusBarH;}if (actionBar) {listItemRect[1] = listItemRect[1] - actionBerH;}listItemSize[0] = listParent.getWidth();listItemSize[1] = listParent.getHeight();
}

listParent是在resolveToFull中保存的gsyvideoplayer在listView的item中的布局参数;

上面的全屏动画在handler的post方法之前,先构造了左边这个布局,listItemRect标注反了,需要互换一下,不好意思,不会画图,将就一下,并把它加入到fullViewContain中;只不过因为demo中的左右边距都为0,所以listItemRect[0]就是0,listItemRect[1]就是上边距再加上statusbar和actionbar的高度

/*** 如果是5.0的,要从原位置过度到全屏位置*/
private void resolveMaterialFullVideoShow(GSYBaseVideoPlayer gsyVideoPlayer) {FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) gsyVideoPlayer.getLayoutParams();lp.setMargins(0, 0, 0, 0);lp.height = ViewGroup.LayoutParams.MATCH_PARENT;lp.width = ViewGroup.LayoutParams.MATCH_PARENT;lp.gravity = Gravity.CENTER;gsyVideoPlayer.setLayoutParams(lp);gsyVideoPlayer.setIfCurrentIsFullscreen(true);
}

在resolveMaterialFullVideoShow方法执行后,变为右边那张图;

至于中间的动画效果:只要 TransitionManager.beginDelayedTransition(fullViewContainer);

科普1:

要改变某个控件的位置,可以用修改布局参数后setLayoutParams,如果想给这个过程加上动画效果,则可以在父节点上调用TransitionManager.beginDelayedTransition

无论是resolveToFull还是resolveMaterialToNormal至此已经完成了全屏变化;两个方法最后都调用了resolveChangeFirstLogic

/*** 是否全屏一开始马上自动横屏*/
private void resolveChangeFirstLogic(int time) {if (isFullLandFrist()) {handler.postDelayed(new Runnable() {@Overridepublic void run() {if (orientationUtils.getIsLand() != 1) {orientationUtils.resolveByClick();}}}, time);}gsyVideoPlayer.setIfCurrentIsFullscreen(true);if (videoAllCallBack != null) {Debuger.printfLog("onEnterFullscreen");videoAllCallBack.onEnterFullscreen(this.url);}
}

该方法是用来实现全屏后的屏幕旋转;

/*** 点击切换的逻辑,比如竖屏的时候点击了就是切换到横屏不会受屏幕的影响*/
public void resolveByClick() {mClick = true;if (mIsLand == 0) {screenType = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);gsyVideoPlayer.getFullscreenButton().setImageResource(gsyVideoPlayer.getShrinkImageRes());mIsLand = 1;mClickLand = false;} else {screenType = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);if (gsyVideoPlayer.isIfCurrentIsFullscreen()) {gsyVideoPlayer.getFullscreenButton().setImageResource(gsyVideoPlayer.getShrinkImageRes());} else {gsyVideoPlayer.getFullscreenButton().setImageResource(gsyVideoPlayer.getEnlargeImageRes());}mIsLand = 0;mClickPort = false;}
​
}

这一块代码病史很难,就是横屏设置Activity为竖屏,竖屏就设置Activity为横屏;以上就是音视频中抖音GsyVideoPlayer视频播放的分析学习了,关于更多的音视频开发学习可以参考《音视频入门到精通手册》要知道关于音视频的开发需要积累许多底层知识,需要一系列的学习途径才能成为一个音视频开发高手。这个文档里面记录了几千个音视频开发小知识。需要可以前往查看。

文末

最后解释一下,从列表视频到全屏视频的过程中,无缝衔接了,原因就是,只是GsyVideoPlayer这个布局换了一个父控件,GsyVideoManager中的medieplayer并没有停止播放,所以视频无缝衔接了。

音视频开发—抖音GsyVideoPlayer视频底层分析使用相关推荐

  1. 抖音矩阵号/抖音短视频SEO矩阵系统源码开发,优化排名。

    前言:抖音矩阵号/抖音短视频SEO矩阵系统源码开发,优化排名. 短视频获客系统支持短视频智能剪辑.短视频定时发布,短视频排名查询及优化,智能客服等,那么短视频seo系统开发时需要开发哪些功能呢?今天我 ...

  2. 简智音科技:抖音短视频时长几何为好呢?

    抖音短视频自全网火的遍地开花后,发展便一路顺风顺水,且在同行类占据金字塔顶端,至今无任何社交媒体软件打破此记录.提及抖音,大多数人不得不提短视频创作功能何其强大,能使人一夜暴富,同时还能影响每个人的生 ...

  3. linux视频应用程序开发,Linux平台音视频开发和音视频SDK应用

    Linux平台音视频开发和音视频SDK应用 下面介绍一款强大的音视频即时通讯平台给大家,它就是--云智真音视频SDK. 云智真提供一套跨平台的音.视频即时通讯解决方案,基于先进的H.264视频编码标准 ...

  4. 抖音用户扫码一键转发视频至抖音php

    需求:用户扫码授权后自动转发视频到抖音,转发完成后获得一张优惠券,视频要随机生成(用阿里云视频生产接口提前后台生成好),因为同一个视频会被抖音降权 我用的yii2框架,具体写法需要自己改一改 1:接入 ...

  5. PR模板 时尚简洁手机竖屏竖版短视频宣传抖音PR模板

    PR模板 时尚简洁手机竖屏竖版短视频宣传抖音PR模板 注意!这些是Adobe Premiere Pro的.Mogrt,您需要在计算机上安装After Effects,并且该版本应与Premiere P ...

  6. 伪原创视频 审核 抖音短视频md5修改在线

             伪原创视频 审核 抖音短视频md5修改在线          内容主打短视频,抖音要怎样"盘活"自己的产品池?以抖音的新晋网红李雪琴为例,以"喊话吴亦凡 ...

  7. 【Android音视频开发】- 实时采集视频

    前言 通过我的上一篇文章,可以知道直播大致有几个步骤:音视频采集 -> 美颜/滤镜/特效处理 -> 编码 -> 封包 -> 推流 -> 分发 -> 解码/渲染/播放 ...

  8. html隐藏视频图标教程,抖音怎么隐藏视频?抖音设置视频权限隐藏视频教程

    抖音怎么隐藏视频? 1.打开手机抖音APP. 2.点击加号,在点击红色圈录制,完成后下一步. 3.在发布页面点击锁图标,选择私密,尽自己可见. 抖音怎么隐藏视频 抖音怎么设置视频权限隐藏视频? 方法/ ...

  9. 视频添加水印 抖音拍摄技巧

             视频添加水印 抖音拍摄技巧          (<抖音隐私政策>很更新仍停留在2018年10月31日)                      在短视频操作中,视频修改 ...

最新文章

  1. Windows和Linux的编译理解
  2. 避免使用aireplay-ng指令时出现AP通道不对的方法
  3. centos安装virtualbox
  4. RabbitMq(十一) 死信交换机DLX介绍及使用
  5. 【java基础知识】设置表格内容居中显示,表格字体大小调整,表格列宽调整
  6. mysql数据库内存分配_MySQL OOM 系列一 Linux内存分配
  7. 博为峰Java技术题 ——JavaSE Swing顶层容器中添加菜单栏
  8. 复制百度文库的文字加什么后缀_下载百度文库文档 怎么快速提取百度文库中可以完整阅读的文档...
  9. 飞思卡尔单片机KEA128之ADC学习
  10. 博士毕业最高201万!华为顶级薪酬招“天才少年”
  11. java点击展示word文档_【java】如何实现在线预览word文档?
  12. 初识马尔科夫链,原来是这样的
  13. 经纬财富:东莞炒白银怎么做好风险控制
  14. 针对rnnoise vad 分享
  15. ElasticSearch索引生命周期管理(ILM)
  16. 博客搬家系列(六)-爬取今日头条文章
  17. 洛谷P5149 会议座位
  18. 6.1. Meanshift和Camshift
  19. 如何把文档转成html格式转换,文档格式转换工具Print2Flash使用教程:如何将文档转换为Print2Flash格式件或HTML5文件?...
  20. 我在蚂蚁金服技术风险部做“医生”

热门文章

  1. 上经 -- 乾【卦一】乾为天(一)
  2. C++自带string类的常用方法
  3. MySQL数据库实验(四):E-R图实例讲解
  4. 软件外包项目如何承接
  5. 019动物识别检测网页版
  6. 如何获取QQ邮箱授权码?
  7. 输入成绩,并计算全班平均分及成绩大于平均分的人数
  8. Ae 效果快速参考:抠像
  9. 非功能性需求都包括哪些方面?
  10. go语言 Golang官网被墙解决办法