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 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,3 @@
1
+ Rails.application.routes.draw do
2
+ match 'videojs'=> "videojs#update", :via => :put
3
+ end
@@ -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
@@ -0,0 +1,7 @@
1
+ # encoding: UTF-8
2
+
3
+ class AddLastPlayedAtToVideojsUserTrack < ActiveRecord::Migration
4
+ def change
5
+ add_column :videojs_user_tracks, :last_played_at, :integer, :comment => "上次播放时间"
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: UTF-8
2
+
3
+ class AddMarkWatchedToVideojsUserTrack < ActiveRecord::Migration
4
+ def change
5
+ add_column :videojs_user_tracks, :mark_watched, :boolean, :default => false, :comment => "手工标记看完"
6
+ end
7
+ end
@@ -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,3 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.expand_path('../rails_engine.rb', __FILE__)
@@ -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: []