准确记录用户观看视频内容时长

您所在的位置:网站首页 怎么破解付费网站观看记录 准确记录用户观看视频内容时长

准确记录用户观看视频内容时长

2024-06-12 18:31| 来源: 网络整理| 查看: 265

文章目录 问题的产生一、从最简单的开始二、天真可爱法三、录点法四、打点法五、暴力打点法六、小结七、大结

问题的产生

to be or not to be, that is a question. 不是问题解决不了,只是你自己不够努力,当然,也可能是你不够聪明0.0。有效地记录用户观看某一视频的总时长,问题的来源在于用户拥有自由意志,可以随意对观看的视频进行 快进 快退,倍数播放等等。那么,对于要拿视频卖钱的主儿们来说,怎么才能精确记录下来呢?

闲话少叙,让我们骑上心爱的小摩托,温柔地开到主题中来。根据爱因斯坦的相对论,我把解决这个问题的方案,大致分为四种:一、天真可爱法。二、录点法。三、打点法。四、暴力打点法。

一、从最简单的开始

正常情况,用户(假设是小王好了)一个视频的播放进度如下:设 视频总时长为10,小王从0开始,看到6他就退出了,也就是A点,那么现在的观看总时长t=6-0=6,如下图; 也就是说视频的播放进度 p 和观看时长 t 是相等的。如果小王在A点没有退出,而是直接快进到了8,然后又看到了9,这时才退出,那么现在的观看总时长t=(6-0)+(9-8)=7;那么这时播放进度 p(=9),和总观看时长t(=7),就显然不一致了。如果小王在B点,也就是8的时候开启了 2倍速度,那么现在的总时长就应该是 t= 6+1*(1/2)=6.5了。。。他和观看进度p(=9)之间似乎更是无情地天人永隔了。 在这里插入图片描述

不过,先别慌,问题不大,我们的难点其实是下面的。我们假设小王看的是一段小视频,其中的某个片断引起了他身体的激烈共鸣,于是他就将这个片断重播了N次,如下图 ,3-5这一段他看了整整30遍,然后在7的时候退出。那么这时p=7,t=7+(5-3)*29=65,这里减掉本来看的一次所以乘29。对于只关心内容进度的来说,重复的29次我并不关心,我想要的是数据是7。。。 在这里插入图片描述

本文讨论的重点也是针对小王到底看了哪些内容,而不是他重复了多少遍。

二、天真可爱法

刚开始,你也许会想,不如我拿一个小王开始播放的时间点 startPoint,再拿一个结束播放的时间点endPoint,两者一减,得一个值 T0,再看一下进度T1,然后看根据产品大人的需要,两者之间取个最小值 或 最大值 好了。对于本文关心的问题,也就是取较小值。即 t=min(T0,T1)。考虑到用户小王同志一次看不完,那这个数据得存服务器,aka 后台。于是最终的计算公式为 t=min(T0,T1),其中T0=endPoint- startPoint + t0-tp,t0就是从后台取的上次的播放时长,tp为中间视频暂停的时长,也就是pauseTime。结束播放时将t丢给后台。但是别忘了我们一开始提出的问题,重复播放和小王对进度条的大气而任性地随意拖动。所以这种方案我们只能希望小王是个天真的人,而使用这个方案的,则是比较可爱的团队。

当然如果只关心用户实际观看总时长(即与内容无关,重复也算上,也与倍数、播放进度、快进快退无关),用这种方案就可以了。下面的东西主要是针对内容敏感的时长,也就是需要计算用户 【播放内容】 占 【视频总时长】 的百分比。

三、录点法

其实我们通过画的进度条图,已经可以发现一些事情的端倪。假设 上一次小王已经看了1秒钟,t0=1,进入播放时直接是1,播放到3又快退到2,然后播放到4又快进到6,看到8的时候离开。如下图。那么他现在一共看的时长应该是t=t0+(3-1)+(4-2)+(8-6),发现在什么了没有,对的,需要去除重复的播放时段。我们只需要记录下用户播放时的进度改变点jumpPoint,然后做成区间,对这个区间再做一个去除重复,就万事大吉了。 在这里插入图片描述

比如我当前记录了用户的一次播放记录为列表list ={[0,7],[1,10],[12,15]}因为[0,7]这个区间在和[1,10]有重复部分,所以我们算的时候就先对记录的表里的数据做一次合并去重算法M,现在的列表就为list={[0,10],[12,15]},现在的总时长很明显就是t=(10-0)+(15-12)。如下图: 在这里插入图片描述

好了,我们的方案就是这么简单,在每一次用户发生jumpPoint的时候,记录下当前的点,放进一个list表中,最后计算总时长。具体怎么做呢?仔细看看以上洒家车的几张简单又优美的刻度图,您就会有所了悟了。那就是在发生 开始播放、 快进、快退、步进、步退,暂停,退出等行为时,记录下当前视频进度点,为了方便,后面 快进快退步进步退 统一称为 jumpPoint,因为他们的实质是一样的,而暂停、退出、结束统一称为pausePoint,因为对于我们研究的问题来讲,他们也是一个东西的。

好了,有了方法了,具体要怎么搞呢?就算你给我一个漂亮的机器人女友,不能不教我怎么激活呀。为了方便理解,本文用全世界都明白的java作示例。首先我们定义一个Section对象,section 有两个字段:startPoint,和endPoint,用来记录一次区间的发生,如section=[3,5],那么startPoint=3,endPoint=5,然后我们需要一个漂亮的列表,为了印象深刻一点,我们就叫它红茶表list吧,用这个list来存放这些Section。看图说话,由A点开始,视频开始播放了,我们先来一个section0,它的startPoint就记录下来为1(A点),section0=[1,]。假设看到4(B点)的时候,快退到C点,这时我们section0的endPoint就记录为4(B点)section0=[1,4],同时再来一个section1,将section1的startPoint记录为jump行为发生后的进度3(C点)section1=[3,],假设看到5(D点)用户又快进到7(E点),这时就将section1的endPoint记录为5(D点) section1=[3,5],同时将section2的startPoint记录为7(E点) section2=[7,],9的时候pause了,记录为section2的endPoint , section2=[7,9]。这时我们就得到了list ={[1,4],[3,5],[7,9]}这样一个红茶表。我们将表里的点做一次merge去重,得到list ={[1,5],[7,9]},这就是最终我们要的东西了。 在这里插入图片描述

好了,你说的道理我都懂了,但是臣妾真的做不到呀。这些奇奇怪怪的点要怎么录呀?考虑到很多人会用第三方的播放sdk,或者你说我根本就拿不到这些点的数据,那么您先别急,下边还有办法。这里先把能拿到这些点的东西讲完。

拿到这些点了,你以为就完了吗? 当然如果正常的话,没有其他情况,用户默默地看完了一整个视频,那么我们每次的红茶表里应该就一个section记录,终点减起点,over找小妹子去了。但考虑到这是个复杂的社会,很多人都有着复杂的人生,他们的自由意志不是我们能控制的。万一他就是不停地点拖进度条呢?那你最后的列表不是特别大?那算起来不是很麻烦?所以针对这种情况,我们可以每次有新的露点记录进入红茶表时,都做一次去重算法M,大大优化性能。比如正常情况,第一次录点为,[1,3],list={[1,3]},第二次露点为[2,4],那么第二个点入表的时候,我们可以直接M算法merge掉它,那么红茶表里就是list={[1,4]}。 在实际中,如果不涉及离线视频播放,也就是用户可以下载下来视频观看,那么前端直接算好时长丢给后台就好了,涉及到离线视频播放,那我们就需要把这个做完最后M算法的红茶表丢给后台,后台拿到这个表后,取出数据库的之前存的表,做一次M算法,更新入库。而前端下一次播放的时候,也会先请求后台的表,拿到上次的记录做为初始表,使用此表进行本次优雅的露点行为。考虑到有网络错误,上一次没上传成功的情况,初始的时候还需要检查本地是否存了没有上传成功的表,和后台拿回来的表做一次M算法,做为初始表。

好了,露点法已经介绍完了,相关的示例代码会用java写在最后。至于倍速问题,请耐心看到最后哦亲~

这种方法的性能最高,资源消耗最小,比较推荐。这边呢,建议您仔细研究您的视频播放Sdk,看能不能使用代码注入,方法重载加接口,反射等方式顺利进行露点行为。

四、打点法

在明白了我们要干的情事之后,面临的另一个问题是,很多点我拿不到,于是打点(酱油)法就应运而生。 这里直接说我们要的干的事情,然后再解释它的道理。 我们需要一条计时线程,从视频开始播放后,每隔一段时间d,就打下一个点,记录入表。这里为了方便,取d=5s,也就是每隔5秒打一个点。最初的样子如下:A点打下sA=[0,],B点打下sA=[0,5],sB=[5,],C点打下sB=[5,10],sC=[10,],以次类推。。。初始表如下list={[0,5],[5,10],[10,15]},做一次M算法后,list={[0,15]},最终结果就是15秒。请注意洒家画的乃是一条射线了。 在这里插入图片描述

好了,我们继续进化。 虽然以上的情况会是大多数,但正常情况我们说用户播放中会进行视频jump行为,小王同志说,我跳,我跳,我跳跳跳,诶,你要把我怎么着?下面进入打点 (酱油)法的正式传说。 首先我们要开一条计时线程,从视频开始播放,每隔d开始打点,发生pause行为和jump行为的时候重新打点,为了计算出jumpPoint的具体值,我们需要一个counter,在d时间里,每隔1秒增加1,大于等于d重新从0开始计数,具体的风骚操作如下:

在这里插入图片描述

如图,d=5,那么我们的counter在打点间隔5s内,就重复计数1,2,3,4,5,数到5再循环1,2,3,4,5。正常打点都没什么问题,如图,假设我们正常打了点,红茶表list={[0,5],[5,10]},优化起见,每次新点入表进行一次M算法,现在list={[0,10]},然后在未来的某个点X,小王同志发现昨晚忘了喂猫,于是快退到了6s,这个G点。如图,我们A,B,C,每个点都打好了点,现在的状态用伪代码可表示为list={[0,10],[10,]},其中sX=[10,]。那我们要做的,就是找出X点的值,因为让人兴奋的G点是点击跳转的点,它的值我们肯定拿得到。所以jump行为发生后,我们需要保持list表现在的状态,重新打个新点,即在G点处,新记一个section,sG=[6,],然后我们那条计时的线程从C点开始的一个d(=5s)跑完了,到了H,这个时候,敲黑板!就比较重要了!我们看图说话,从C点到X,G,H,现在经过的总时间是5s,假设现在H点的值是8,那么t(H-G)=8-6=2,那么t(X-C)=d-t(H-G)=5-2=3,那么X点的值,pX=10+t(X-C)=10+3=13,实际上 t(XD)=t(GH),这时我们就知道了sG=[6,8],sX=[10,13],将两个section入表list={[0,10],[10,13],[6,8]},同时H点重新开始新一轮的打点sH=[8,],list做一次合并merge算法list={[0,13],[8,]}。 大家可能会问,那快退呢?快退其实是一样的道理,这理不再重复了,有兴趣可以自己画图算一下就很明白了。那么我们的counter来干嘛?counter没卵用吗?还是拿刚刚的例子,counter的作用,是在于X点跳转后,用户在未到达H点的时候就pause掉了。假如我在P点pause掉了,现在一个d还没计时完,所以刚刚的算法就没办法进行,思前想后,也只好加个counter。比如小王在C点之后,看到X点发生了jump,jump到G点,还没到我们的d跑完,也就是还没到打点的地方,没办法只能看现在counter的值了,比如counter现在的值是4,P的值是7.1,那么t(P-G)=7.1-6=1.1,,那么t(X-C)=4-1.1=2.9,那么现在sG=[6,7.1],sX=[10,12.9],那么list={[0,10],[10,12.9],[6,7.1]},做一次M算法之后,list={[0,12.9]}。 很明显,这种方式会有误差,而且误差与counter的精细度成反比,比如counter每隔0.5s计一次,那么由于pause造成的误差就会更小。 另外,由于有倍速播放,所以我们计算t(X-C)的时候,应该除以倍数speed,才能得到更为精确的值。拿最开始的例子来讲,从C到X,到G到H,其他值不变,但若是1.5倍播放,那么t(H-G)=(8-6)/1.5=1.33,那么 t(X-C)=5-1.33,即d(X-C)=t(X-C)/1.5=5.5,这里的d(X-C),表示从C到X进度条往前跑的长度,所以X的值就是15.5,那么现在list={[0,10],[6,8],[10,15.5]},使用SM算法之,list={[0,15.5]}。 综上,打点法,很考验初中数学知识,要精确点的话,很烦琐。而且每次pause行为都会面临不同程度的精度丢失。

五、暴力打点法

打点法真的是一种无比烦琐而蛋疼的打法呀,那有没有简单粗暴一点的办法呢?诶,今天您算是问对人了。下面呀,就让我们跟随作者的脚步,一起走进 暴力打点法 背后,那不为人知的秘密。

很简单,在视频开始播放后,直接开一条线程,每隔1s打一个点,如果某个section的startPoint和endPoint差值的绝对值大于1,则直接丢弃该section。如下图:正常情况,我们会得到一个这样的红茶表list={[0,1],[1,2],[2,3],[3,4]…}如果某个section长成这样[5,9],或者[6,4.5]那我们就判定为这两个section是发生了快进 或者快退之类的jump行为,直接无情地丢掉它。list={[0,1],[1,2],[2,8],[8,4],[4,5]}那么丢弃掉[2,8],[8,4]后,list={[0,1],[1,2],[4,5]},使用SM算法暴之,list={[0,2],[4,5]}。实际上每打一个点我们就可以做一次M算法,大大提高性能,需要丢弃的section直接判断不入list即可。在实际应用中,播放器的时间单位基本上都是ms,所以判断某个section该不该丢弃,可以这样 abs(startPoint-endPoint)>1000 ms,考虑到播放进度中的网络延迟等情况,可以将判断标准适当放宽一点,比如 abs(startPoint-endPoint)>1100 ms。这样基本就o了,非要再精确点,再除以现在的倍数就好了,即 abs(startPoint-endPoint)>1100 ms/speed。

在这里插入图片描述

这种方式相较于打点法,简单易行,就是性能上会差些,毕竟每隔1s都要做一次记录section入表和M算法,而且用户每一次jump行为就会丢失一次精度,增加一丢丢误差。

六、小结

实际上,不同前端可以选择自己中意的方式进行录表,比如pc端可以用打点法,IOS可以用录点法,他们都是基于录点去重的方案,这也是此种方案的优点之一。

下面简单地写两段代码好了,用以卑微地表示这是一篇技术文章:

Section可以定义成这样:为什么要放speed在这里我也忘了,也许用得上,也许用不上,望少侠自行斟酌。

public class Section { private long startPoint; private long endPoint; private float speed; public Section() { } public Section(float speed) { this.speed = speed; } public Section(long startPoint, long endPoint) { this.startPoint = startPoint; this.endPoint = endPoint; } /*get set 省略*/ }

然后我们的M法算长这样。这里我只是随手写了一种比较容易理解的方式,当然还有其他很多高效的算法,各位大佬都那么聪明,请自行研究了。简单的测试了下,好像没啥问题,实际不知道,有问题欢迎随时反馈在下边的评论里,以便我及时的不作处理。

import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; /** * Author:v * Time:2021/4/22 */ public class MergeUtil { public static void main(String[] args) { final LinkedList redTeaList0 = recordedList0(); final List redTeaList1 = recordedList1(); // mergeList(redTeaList0); // getTotalTime(redTeaList0); mergeList(redTeaList1); getTotalTime(redTeaList1); } private static long getTotalTime(List list) { if (list == null || list.size() == 0) { return 0L; } long t = 0L; for (Section s : list) { t += s.getEndPoint() - s.getStartPoint(); } System.out.println("Total time is:" + t); return t; } public static List mergeList(List rawList) { if (rawList == null || rawList.size() == 0) { return rawList; } System.out.println("********before sort*******************"); printList(rawList); rawList.sort(new SectionComparator()); System.out.println("********after sort*******************"); printList(rawList); merge(rawList); System.out.println("********after merge*******************"); printList(rawList); return rawList; } private static void merge(List rawList) { ListIterator iterator = rawList.listIterator(); Section tmp = iterator.next(); //System.out.println("tmp " + tmp.toString()); while (iterator.hasNext()) { Section next = iterator.next(); // System.out.println("next " + next.toString()); if (mergeSuccess(tmp, next)) { iterator.remove(); } else { tmp = next; } } } private static boolean mergeSuccess(Section pre, Section next) { if (next.getStartPoint()


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3