聊一聊播放器控制层的Scroll吧

从开始做android开发,一晃就两年十个月了, 算上实习的公司已经经历了三家公司, 不知道是巧合还是怎么
三家都是做跟视频有关系的. 今天的记录跟视频有关系但是关系不打,主要是讲视频的控制层.
现在回头想想之前做播放器控制层的时候,基本都是没有动过脑子的, 关于MotionEvent 也是一知半解,写出来的控制层的代码,
都是从网上搜索到的.然后我看了看几家公司的代码, 基本上大家都是雷同的…

下面贴出来一个 示范代码, 网上随处可见.
Paste_Image.png

反正这代码的逻辑就是算touch Down 下时的Event xy 坐标 然后看当前的Event 坐标 来判断用户 是做的什么操作,以及滑动了多少. 这段代码 大部分时间应该都是没问题的, 大家也都是这样做的.
但是在用户做一些特殊操作的时候 就会有问题.

我举个例子:

用户touch 到了屏幕中间,然后向右滑动, 然后滑动到最右面, 不要松手 往左滑动.再滑动到中间继续往左滑.
你就会发现touch事件有断层, 并且滑动距离与 视频进退的比例不好设定. (这点尤为突出)

这些缺点 以及一些不如意的地方, 由于我描述能力不行, 可能说的不是太清楚, 只有实际开发碰见过这些问题的同学才会明白我说的是什么..

之前自己也一直没有注意到这些细节上的问题, 偶尔的一次机会发现了这个体验问题, 决心要解决它,重新去搞一套比较舒服的Scroll 算法. 最后在烨哥+飞哥的共同帮助下,完成了一套暂时看起来还不错的Scroll算法, 所以写下了这篇文章来记录一下想法和实现.

以下是正文

首先 我们要明确一件事情,

当用户手指touch down 到屏幕的一瞬间, 整个滑动过程, 用户滑动距离与视频改变的时间的比例,就已经确认了, 不能再因为任何原因改变, 一直到用户手指离开屏幕.

timeline

看图

上面这条线是影片的时间线,
0 就是视频起点
d 就是影片的总长度
Y 当前currentPosition
下面这条线是用户touch点在屏幕上的位置线
p是触摸左侧X坐标
q 触摸右侧X坐标
X touchDown的X坐标

其中需要说明的是 这里有个小细节, 很多人算触摸界限的时候,都习惯性设置0 和 ScreenWidth. 但是其实真正使用的时候, 是没有人会从0开始滑 或者滑到屏幕最右面的, 除非你是测试.
所以 p 是设置的1个padding值,并不是 0, q 也不是ScreenWidth. 一般是ScreenWidth - padding.

这套代码的整体思想很简单, x(用户的触摸x坐标)随着用户的滑动不停的在改变, 我们要用x来算出相应的y(影片seek到的位置), 也就是一个二元一次方程. y = ax + b; 所以我们要来先算出 a 和 b的 值.

init
首先是一些常量的初始化, 注释也都有说明,大家自行理解一下.
leftTouch 就是手指x坐标离左面的值
rightTouch 是离右面的值.
touchRange 是总的触摸长度.
rightDuration 是影片剩余的时间.

接着上面的说, 我们既然要算出a 和 b, 那么就需要 得到两组合适的 xy 值 代入公式.
其中已经确定好的一组值 就是 X 和 Y 就是手指刚落下的时候 那一组. 我们需要做的就是找出另外一组x y 值.

情况其实只有四种

  1. 以屏幕触摸终点 对应影片的Duration.
  2. 以屏幕触摸中点 对应影片的Duration.
  3. 以屏幕触摸起点 对应影片的起点
  4. 以屏幕触摸中点 对应影片的起点

可以根据比较 x 与touchRange的比值 与 y 与duration的比值的大小,来确定具体使用哪种情况.
比较口头的来说,也是有四种情况, 可以涵盖全部情况

  1. 影片已经播放的时间 比较短, 触摸的位置特别靠右.
  2. 影片已经播放的时间 比较短, 触摸的位置靠右,但是不是特别靠右
  3. 影片已经播放的时间 比较长, 触摸的位置特别靠左,
  4. 影片已经播放的时间 比较长, 触摸的位置靠左,但是不是特别靠左

具体代码如下,也是最核心的代码.

code

这个地方如果理解起来有点困难, 可以画一些图来感受一下,就能明白为什么这样对应了.
得到 两组 xy 值以后, 代入二元一次方程, 算出 ab 以后就好说拉!

final

以上就是整套Scroll的代码和解析了, 实际跑起来的效果特别好! 顺滑无比! 怎么滑动都不会有断层,而且无论用户触摸在哪里开始滑动,
滑动距离与影片改变的时间都很舒服.

Share Comments

你就是我的天使.

相识

相识皆是偶然,几年前因为游戏的缘故和你相识, 最初因为天各一方,加上这样那样的原因,一直只是远远的和你沟通过,沟通的内容也大多是与游戏相关.

缘起

第一次对你产生兴趣竟然是偶尔的一次深夜, 你推荐给我了一首歌, 名字叫唉声叹气; 应该算是一个很冷门的歌曲了,并且是听不懂的粤语,但是不知道为什么深得我心.
在单曲循环了很多很多个夜晚以后, 开始对你这个小姑娘产生了兴趣, 然后你又陆续给我介绍了好几首歌曲, 都是我喜欢的那种. 陆陆续续发现了很多和你聊的来的地方
以及三观出乎意料的一致.
但是依然是由于这样那样的原因,并没有让我往前走出那一步. 每一次动心都默默的只是动心而已.

记着有一天下午和你聊天,聊到双方所期望的对方是什么样的,说到深处突然发现你我或许有一点合适. 但是因为分隔两地,这个话题也就戛然而止了.
时间就这样一天天的过, 转眼认识了两年多, 有一天你突然跟我说, 想离开家乡来北京闯一闯,看看外面的世界, 说真的, 当时我内心是激动的开心的,
但是很快冷静下来,沉住了气, 开始帮你分析利弊,以及如何说服家里.
内心是充满着期待和好奇的, 开始盼着你的到来.
但是我也告诉了自己,其实一切很难, 无论是 你摆脱家里的一切来到北京, 还是我摆脱一切的束缚,迈出那一步.

相见

等待是煎熬的,漫长的.
你说明天就要来北京了, 嘴上说着好啊好啊, 但是心里还是不敢彻底相信, 担心着这样那样的变数.
直到那个午后, 你如同一个小天使一样蹦蹦哒哒的出现在我面前,
我嘴上说着hi. 心里却说着 终于等来了你.

很快一切的一切都变得脆弱不堪一击, 我的世界里全部的打算 计划 担忧 迷茫 都变成一句话
我爱你,我要不顾一切和你在一起.
你让我明白了这世界上最美好的事, 就是我爱上了你,你也爱我.

你让我奋斗变得更有意义.
你让我对明天充满着期待.
你让我变得不可自拔.
你让我明白,你就是那个陪我走到最后的人,让我可以付出一切的人.

此生只求与你相伴终老.

午后 阳光下
一起吃火锅

Share Comments

做lib的时候学习到的一种思路.

废话不多说,前几天我在我的支付lib 中,提交了一段很简单的代码,可是cto却指出了有严重的问题.
代码是这样的.

code

代码很简单也很容易理解, 我这是一个支付lib, 然后这个类提供了1个发起支付的函数,
我拿到外部传给我的Charge 进行检验,检验完毕后,我会调用Ping++ 发起支付.
其中那个Callback 就是对各种支付结果进行反馈的.
是不是看起来没有什么问题?

但是我的cto 看到后 立马就指出来了一个非常严重的问题 是这样的.

当外部调用这个方法的时候, 外部一般是这样的

show a loading Dialog –> xx.createPayment(xx, xx, Callback);
然后在 Callback{
onPaySuccess();
onPayFailed();
} 的回调中, 隐藏掉这个dialog.

ok 看起来一切都是正常的.
但是cto说,你要明白你是做lib的, 你是无法控制外部何时显示dialog的, 你需要做的是保证无论什么情况都能让调用者
收到callback.
但是你这样写法的话, 如果外部是这样写的
xx.createPayment(xx, xx, Callback); –> show a loading Dialog ;
然后它恰巧OrderId 不符合规定,那么 他就会出现先收到错误的回调 然后显示dialog的情况.
然后他的那个dialog 就再也不会消失了…

然后正确的写法应该是这样的.
final code

相信你已经看懂了问题所在,通过post到队列的最后,来保证时序是正确的.

感谢烨哥,学到了很多.

Share Comments

播放器重构中遇见的坑

播放器重构中遇见的坑

最近一直在重构公司项目的播放器项目, 这次重构是借鉴了Bilibili开源的ijkPlayer 代码的 写法, 一个VideoView 对应各种播放器内核.
(android mediaplayer, exoplayer, ijkplayer.)
rendenview 与 Mediaplayer 层面 隔离开.

在开发过程中遇见了几个小坑.

  • 播放器状态相关
    简单的来说, mediaplayer 和ijkplayer 在进入Preparing的状态中时, 是不会对视频本身进行缓冲的, 而仅仅是做了初始化播放器的工作, 也就是说在prepared完成之前, 是不会有buffer的生命周期回调的, 而是在prepared 之后, 才会开始进入buffering state. 但是exoplayer的理念和 这两者有一点小小的不同,
    exoplayer在 进入 preparing状态之后 初始化完成播放器以后,
    会对视频本身开始buffer, buffer到足够开始进行播放以后, 才会
    回掉 STATE_READY(Prepared)的状态.
    这会给上层做整套生命周期 造成一定的影响.

    如图:
    androidmediaplayer/ ijkplayer 的初始化生命周期
    IDLE –> Preparing –> Prepared –> buffering –> playing.
    exoplayer的 初始化生命周期
    IDLE –> preparing –> buffering –> prepared(ready)

  • onCompletion 相关
    这三个播放器都有onCompletion的回调,这个回调其实很好理解,就是播放完成的时候会回调该生命周期,
    但是ijkplayer回调的时机是有问题的, 它不仅仅在播放完成的时候会回调该方法,而且在出现错误的时候也会回调该方法。
    播放器产生错误 –> onError –> onCompletion.
    我也跟IJK的作者提了issue;
    作者也承认了这个错误, 但是 他说 这并不重要, not on plan.

  • SeekTo 相关
    android mediaplayer seekTo 到 mediaplayer.getDuration(); 也就是说 seek到最后 正常来说 是没有什么问题的.
    可以正常seek, 然后成功进入completed状态. 然后这个时候调用start. 也不会有什么问题 会进入Playing状态
    但是你如果再次seekTo mediaplayer.getDuration(); 再点播放 就会出问题了, Mediaplayer 内部状态已经混乱了.
    对于这件事 我不知道为什么网上搜不到很多相关的问题, 可能是我太无聊了或者我使用方法不恰当的问题把.
    总而言之最后我做了WorkAround.
    每次执行seekTo的时候 我都会判断1下 是不是seekTo到最后 如果是 我就seekTo到getDuration() - 2000;
    然后 问题解决了

不过还好 这些问题如果理解的好,可以在上层进行规避.把三套播放器的生命周期 可以整合一套的。

Share Comments

上传Android Library 到 bintray

上传Android Library 到 bintray.

给公司写的videoplayer模块version one 完成了, 公司两个项目都需要用到这个模块,并且其中一个项目外包出去了,所以就要把这个模块上传到一个公共的lib库,方便外包的哥们去调用. 以及后续我这边更新维护. 然后选择了上传到bintray这个网站上.

说实话不是太了解这块儿东西,来这个公司之前一直是闷头写代码层的东西,每天就对着svn和eclipse, 什么maven,git,As,jcenter,bintray 等等完全没接触过. 谢谢公司给了机会去学习这些,但由于英语不好,在学习使用的途中踩了很多坑.
这篇文章就是记录我把这个模块传到 bintray上 整个过程踩过的坑.

第一步 注册

首先你需要 去bintray官网 注册个账号,这里就不过多描述了.你可以的.
注册完毕以后 点击这里 Paste_Image.png
进入maven管理界面
然后 点击 Paste_Image.png
创建新的package

Paste_Image.png

name 就是项目的名称
licenses 我选择的是apache-2.0
version control 我填的是github上的地址
然后一切ok, 点击create package. 创建完毕.

#第二步 寻找好用的publisher
作者一开始不懂这个是干什么的,是公司的cto给我推荐了1个github地址,让我去学习那个,并且告诉我这个是最简单的上传方式.
地址是 novoda
其实英语好的哥们看到这应该已经知道怎么弄了

Paste_Image.png

第一步 把这些配置到 你要发布的项目的 build.gradle文件中 这里需要注意的是
apply plugin: ‘com.novoda.bintray-release’ 这句话 需要放在com.android.library 下面
如图

Paste_Image.png

第二步
Paste_Image.png
在build.gradle 文件中添加这个方法块。
其中userorg 就是你的用户id
groupid 就是你的唯一包名
artifactId 是你之前最初在bintary的maven中创建的那个package的名字,这里需要对应上 否则会上传失败.
publishVersion 就是你的版本id咯
下面2个随意填了

第三步
$ ./gradlew clean build bintrayUpload -PbintrayUser=BINTRAY_USERNAME -PbintrayKey=BINTRAY_KEY -PdryRun=false
在命令行中输出这些,其中需要把BINTRAY_USERNAME 替换成你的bintary的名字
BINTRAY_KEY 替换成你的 BIntray的key ,其中key 可以点击你bintary中头像旁边的edit.
进入API-Key 可以看到你的key,然后 copy进来就好了!
这个时候打回车! 然后就run起来了就好了.
上传成功以后 你可以在你bintary 那个项目中 的管理页面看到这个

Paste_Image.png

如果看到1.0.0 恭喜你 那就是上传成功, 但是这只是1个私人的,还不能让别人都用
这个时候你需要点下面那个add to jcenter 然后申请加入jcenter
用英语写几句描述 然后 提交 然后 就耐心等待就好了!!

#哦 对了
有可能会报一个 :lint 什么什么找不到的错误 我在这卡了好一会儿.
需要在android中添加这么一句 如图 就是那个lintOptions什么什么的

Paste_Image.png

哦哦我想起来了 有可能会出现什么tools.jar找不到的 bug
这个解决比较简单 有可能是你机器的JAVA_HOME没有指对
比如我是rmbp 最初的JAVA_HOME是指向的mac 自带的1.6 jdk.
修改成 androidstudio 指向的1.7的jdk 后就好了!!!

Share Comments