videojs_user_track 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +36 -0
- data/app/assets/javascripts/videojs_user_track.js +84 -0
- data/app/controllers/videojs_controller.rb +20 -0
- data/app/models/videojs_user_track.rb +66 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20130401075529_create_videojs_user_track.rb +18 -0
- data/db/migrate/20130530085750_add_last_played_at_to_videojs_user_track.rb +7 -0
- data/db/migrate/20130607080640_add_mark_watched_to_videojs_user_track.rb +7 -0
- data/lib/rails_engine.rb +16 -0
- data/lib/videojs_user_track.rb +3 -0
- data/videojs_user_track.gemspec +12 -0
- metadata +55 -0
data/README.markdown
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
在video-js框架里,记录用户重复播放视频里各部分的频度。
|
2
|
+
==================================================================
|
3
|
+
|
4
|
+
### 需求
|
5
|
+
判断教学视频如何被用户观看的效果,最简单的是看用户如何重复播放或跳过播放。
|
6
|
+
|
7
|
+
### 技术详解
|
8
|
+
初始化一个长度为要播放视频的以秒为单位长度的数组,各元素初始为零。如果视频正在播放,则给该秒加一; 重复播放,则继续加一。
|
9
|
+
|
10
|
+
### 使用
|
11
|
+
1. 添加videojs_user_tracks数据库表,bundle exec rake db:migrate
|
12
|
+
2. 实现VideojsUserTrack_Rails::Auth#auth方法
|
13
|
+
3. 在js里引用//= require videojs_user_track.js
|
14
|
+
4. 配置js参数,比如 _V_.user_track(eoe.video_player, section_id, eoe.uid, {uhash: eoe.uhash})
|
15
|
+
5. 播放一个视频,看看Rails log或数据库里是否出现了更新
|
16
|
+
|
17
|
+
### 用户认证
|
18
|
+
在config/initializers/videojs_user_track_rails.rb添加如下代码,并实现你的auth方法
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
module VideojsUserTrack_Rails
|
22
|
+
module Auth
|
23
|
+
def auth
|
24
|
+
# your auth code, it can acces the scope in videojs controller
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
### TODO
|
31
|
+
1. 支持其他浏览器内视频播放框架,去video-js化
|
32
|
+
2. 管理页面
|
33
|
+
|
34
|
+
|
35
|
+
### License
|
36
|
+
MIT
|
@@ -0,0 +1,84 @@
|
|
1
|
+
/*
|
2
|
+
* VideoJS视频播放秒数监控
|
3
|
+
* 两个setInterval对播放数组做 浏览器本地 和 服务器 同步。
|
4
|
+
*
|
5
|
+
* 附加 断点播放时间记录(为了重复利用setInterval事件以节省资源)
|
6
|
+
*
|
7
|
+
*/
|
8
|
+
window.videojs_user_track = function(videojs, video_id, uid, auth_data_hash) {
|
9
|
+
if (!window.jQuery) { throw("Please require jQuery first"); }
|
10
|
+
if (!window._V_) { throw("Please require videojs first"); }
|
11
|
+
|
12
|
+
var local_a = [];
|
13
|
+
var last_second; // 用于排重,因为实际执行的setInterval一秒很可能超过一秒,所以和上一个读取值比较即可。
|
14
|
+
var video_length = 0;
|
15
|
+
auth_data_hash = (auth_data_hash || {});
|
16
|
+
|
17
|
+
var store_to_local = function(idx) {
|
18
|
+
if (idx != last_second) {
|
19
|
+
local_a.push(idx);
|
20
|
+
last_second = idx;
|
21
|
+
// 记录断点播放
|
22
|
+
if (idx > 5) { auth_data_hash.last_played_at = last_second; }
|
23
|
+
}
|
24
|
+
};
|
25
|
+
var sync_to_server = function() {
|
26
|
+
// save tmp length, to avoid another function modify it.
|
27
|
+
var _l = local_a.length;
|
28
|
+
if (video_length === 0) { video_length = Math.round(videojs.duration()); }
|
29
|
+
|
30
|
+
if (_l !== 0) {
|
31
|
+
var remote_a = local_a.splice(0, _l);
|
32
|
+
// TODO unshift if failure
|
33
|
+
$.ajax({
|
34
|
+
type: 'PUT',
|
35
|
+
url: '/videojs.json',
|
36
|
+
data: $.extend(auth_data_hash, {
|
37
|
+
video_length: video_length,
|
38
|
+
seconds: remote_a,
|
39
|
+
video_id: video_id,
|
40
|
+
uid: uid
|
41
|
+
})
|
42
|
+
}).done(function (data, textStatus) {
|
43
|
+
if ((data.result + '').match(/fail/)) {
|
44
|
+
clearInterval(intevals.local);
|
45
|
+
clearInterval(intevals.server);
|
46
|
+
console.log(data, "Stoped two setIntevals");
|
47
|
+
}
|
48
|
+
});
|
49
|
+
}
|
50
|
+
};
|
51
|
+
var inspect = function() {
|
52
|
+
console.log((new Date()), ' current seconds is ', local_a);
|
53
|
+
};
|
54
|
+
|
55
|
+
var intevals = {};
|
56
|
+
// TODO 优化,为了避免循环,只在播放时执行,也可以考虑缓存videojs.paused()
|
57
|
+
// 存储播放时间到浏览器本地
|
58
|
+
intevals.local = setInterval(function() {
|
59
|
+
if (!videojs.paused()) {
|
60
|
+
// NOTICE 有时可能因为浏览器资源竞争而导致部分秒没有获取
|
61
|
+
var time_idx = Math.round(videojs.currentTime());
|
62
|
+
store_to_local(time_idx);
|
63
|
+
}
|
64
|
+
}, 333);
|
65
|
+
|
66
|
+
// 同步播放时间到服务器
|
67
|
+
// 不和存储同步是会因为函数阻塞而错过部分时间段
|
68
|
+
setTimeout(function() {
|
69
|
+
intevals.server = setInterval(function() {
|
70
|
+
sync_to_server();
|
71
|
+
}, 3000);
|
72
|
+
|
73
|
+
// 在关闭窗口或离开页面前同步
|
74
|
+
// 得允许多个beforeunload事件
|
75
|
+
$(window).bind('beforeunload', function(event) {
|
76
|
+
console.log("send watching data before close the window...");
|
77
|
+
sync_to_server();
|
78
|
+
});
|
79
|
+
}, 5000); // 需观看5秒以上才考虑同步服务器
|
80
|
+
|
81
|
+
};
|
82
|
+
|
83
|
+
// 测试对浏览器内存和CPU基本无影响。视频播放完后,内存稳定在130.64MB,即使是重复播放。
|
84
|
+
// TODO 整理成videojs plugin,但目前videojs master似乎有bug,编译的js运行出错,有时间改下。
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
class VideojsController < ApplicationController
|
4
|
+
include VideojsUserTrack_Rails::Auth
|
5
|
+
|
6
|
+
def update
|
7
|
+
@result = if auth
|
8
|
+
ut = VideojsUserTrack.find_or_create_by_video_id_and_uid(params[:video_id], params[:uid])
|
9
|
+
ut.update_attributes(:video_second_length => params[:video_length]) if ut.video_second_length.to_i.zero?
|
10
|
+
ut.inc_seconds(params[:seconds], params[:last_played_at])
|
11
|
+
'success'
|
12
|
+
else
|
13
|
+
'auth failure'
|
14
|
+
end
|
15
|
+
|
16
|
+
respond_to do |format|
|
17
|
+
format.json { render :json => {:result => @result}, :status => 200 }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
class VideojsUserTrack < ActiveRecord::Base
|
4
|
+
attr_accessible :video_id, :uid, :video_second_length, :status, :mark_watched
|
5
|
+
|
6
|
+
# like array index in Ruby, idx begins with 0
|
7
|
+
def inc_seconds seconds, last_played_at = nil
|
8
|
+
return false if seconds.blank?
|
9
|
+
seconds = seconds.map(&:to_i).uniq
|
10
|
+
return false if seconds.detect {|second| (second > ut.video_second_length) || (second < 0)} # 大于video_second_length的秒一定不合法
|
11
|
+
|
12
|
+
seconds.each do |second|
|
13
|
+
data[second] ||= 0
|
14
|
+
data[second] += 1
|
15
|
+
data[second] = 9 if data[second] >= 10 # 最多只记录9次
|
16
|
+
end
|
17
|
+
self.last_played_at = last_played_at if not last_played_at.to_i.zero?
|
18
|
+
|
19
|
+
resave!
|
20
|
+
end
|
21
|
+
|
22
|
+
# 返回未看的 最大连续区域的首个索引
|
23
|
+
def max_unwatched_range_begin_at
|
24
|
+
data_str = data.join
|
25
|
+
# 区分零和非零区域
|
26
|
+
flags = data_str.scan(/[1-9]+|0+/)
|
27
|
+
flag_zero_max = flags.select {|i| i.to_i.zero? }.max {|i| i.length }
|
28
|
+
return 0 if flag_zero_max.nil?
|
29
|
+
data_str.index(flag_zero_max) + 1
|
30
|
+
end
|
31
|
+
|
32
|
+
def played_percents
|
33
|
+
return 0.0 if data.size.zero?
|
34
|
+
data.reject {|i| i.to_i.zero? }.size / data.size.to_f
|
35
|
+
end
|
36
|
+
|
37
|
+
def finished?
|
38
|
+
# 避免视频没有初始化
|
39
|
+
(video_second_length > 10) &&
|
40
|
+
# 总时长误差不超过5秒
|
41
|
+
((data.length - video_second_length).abs <= 5) &&
|
42
|
+
(
|
43
|
+
# 判断中断次数是否超过总秒数的二十分之一,否则就算看完。
|
44
|
+
# 比如60秒的视频,里面没看的只有3秒,不管连续还是不连续,就算看完了。
|
45
|
+
(played_percents >= 0.95) ||
|
46
|
+
# 或者强制标记看完
|
47
|
+
self.mark_watched
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def data
|
52
|
+
@data ||= (begin
|
53
|
+
a = JSON.parse(ut.status.to_s) rescue []
|
54
|
+
(a[ut.video_second_length - 1] ||= 0) if not ut.video_second_length.zero?
|
55
|
+
a.map(&:to_i)
|
56
|
+
end)
|
57
|
+
end
|
58
|
+
|
59
|
+
# alias user_track
|
60
|
+
def ut; self end
|
61
|
+
def resave!
|
62
|
+
ut.status = data.to_json
|
63
|
+
ut.save!
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
class CreateVideojsUserTrack < ActiveRecord::Migration
|
4
|
+
def up
|
5
|
+
create_table :videojs_user_tracks, :options => 'ENGINE=Innodb DEFAULT CHARSET=utf8', :comment => "每个用户的播放视频小节的时间频度" do |t|
|
6
|
+
t.string :video_id, :default => 0, :comment => "视频ID"
|
7
|
+
t.integer :uid, :default => 0, :comment => "用户ID"
|
8
|
+
t.integer :video_second_length, :default => 0
|
9
|
+
t.text :status, :comment => "初始化一个长度为要播放视频的以秒为单位长度的数组,各元素初始为零。如果视频正在播放,则给该秒加一; 重复播放,则继续加一。示例格式为" # copied from README
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
add_index :videojs_user_tracks, [:video_id, :uid]
|
13
|
+
end
|
14
|
+
|
15
|
+
def down
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/lib/rails_engine.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module VideojsUserTrack_Rails
|
4
|
+
module Auth
|
5
|
+
end
|
6
|
+
|
7
|
+
class Engine < Rails::Engine
|
8
|
+
initializer "videojs_user_track.load_app_instance_data" do |app|
|
9
|
+
app.class.configure do
|
10
|
+
['db/migrate', 'app/models', 'app/controllers'].each do |path|
|
11
|
+
config.paths[path] += VideojsUserTrack_Rails::Engine.paths[path].existent
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'videojs_user_track'
|
3
|
+
s.version = '0.1'
|
4
|
+
s.date = '2013-04-01'
|
5
|
+
s.summary = File.read("README.markdown").split(/===+/)[0].strip
|
6
|
+
s.description = s.summary
|
7
|
+
s.authors = ["David Chen"]
|
8
|
+
s.email = 'mvjome@gmail.com'
|
9
|
+
s.homepage = 'https://github.com/eoecn/videojs_user_track'
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: videojs_user_track
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- David Chen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-04-01 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: 在video-js框架里,记录用户重复播放视频里各部分的频度。
|
15
|
+
email: mvjome@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- README.markdown
|
21
|
+
- app/assets/javascripts/videojs_user_track.js
|
22
|
+
- app/controllers/videojs_controller.rb
|
23
|
+
- app/models/videojs_user_track.rb
|
24
|
+
- config/routes.rb
|
25
|
+
- db/migrate/20130401075529_create_videojs_user_track.rb
|
26
|
+
- db/migrate/20130530085750_add_last_played_at_to_videojs_user_track.rb
|
27
|
+
- db/migrate/20130607080640_add_mark_watched_to_videojs_user_track.rb
|
28
|
+
- lib/rails_engine.rb
|
29
|
+
- lib/videojs_user_track.rb
|
30
|
+
- videojs_user_track.gemspec
|
31
|
+
homepage: https://github.com/eoecn/videojs_user_track
|
32
|
+
licenses: []
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ! '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 1.8.25
|
52
|
+
signing_key:
|
53
|
+
specification_version: 3
|
54
|
+
summary: 在video-js框架里,记录用户重复播放视频里各部分的频度。
|
55
|
+
test_files: []
|