videojs_user_track 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|