3. 視聴ログ出力機能

本機能は学習者がBO(Book Object)を表示した場合、発生するイベントを動画視聴ログとして出力する機能である。

3.1. イベント

本システムのログデータを理解するためには、本システムが扱う「イベント」についても理解しておく必要がある。

3.1.1. イベントとは

イベントとは、LTIシステムで、学生が学習コンテンツを視聴した時と行動を記録するために定義された「きっかけ」を記録する。イベントの発生源は、

  1. video.js

  2. ChibiCHiLOのフロントエンド が、ChangePage Eventを送出している。

がある。

3.1.2. ログとして出力するイベント

video.jsは、多数のイベントを取得できるが、現時点のシテスムでは、下記のイベントのみをログに書き出す。

詳細は、個別に記述する。

イベント

イベントとは別に取得した情報

video.jsとの対応

firstplay

なし

Video.js v8への移行に伴いfirstplayイベント削除

play

first

play

pause

なし

pause

seeked

シーク操作前のビデオ再生位置

seeked

ended

なし

ended

ratechange

変更した再生速度倍率

ratechange

trackchange

字幕切り替え後の言語

change

forward

なし

back

なし

beforeunload-ended

なし

pagehide-ended

なし

unload-ended

なし

hidden-ended

なし

current-time

なし

changepage

切り替え先のマイクロコンテンツID

以下の目的で取得している

firstplay

学習者がページを開いて最初にビデオを再生開始した時間

videojsのイベントを利用

videojsでは,最初にビデオを再生開始すると,最初にfirstplayのイベントがとれた後,playのイベントもとれるので,firstplayはいらないかもしれない.

play

学習者がビデオを再生開始した時間

videojsのイベントを利用

videojsでは,ビデオを一時停止した後,再生開始すると,firstplayのイベントはとれず,playのイベントだけとれる.

初回・トピック切り替えのタイミングで発火するplayイベントのみ"first"を付与.

pause

学習者がビデオを一時停止した時間

videojsのイベントを利用

seeked

学習者がビデオのシークバーを操作した時間

videojsのイベントを利用

シークを始めたビデオの再生位置も知りたくて,以下のように書いて,シーク操作前のビデオ再生位置を別途取得

/* Record the start and end of seek time */
let previousTime = 0;
let currentTime = 0;
let seekStart: number | null;
player.on("timeupdate", function () {
   previousTime = currentTime;
   currentTime = player.currentTime();
});
player.on("|", function () {
   if (seekStart === null) {
      seekStart = previousTime;
   }
});
player.on("seeked", function () {
   sendLog("seeked", player, seekStart?.toString());
   seekStart = null;
});
ended

学習者がビデオを最後まで視聴した時間

videojsのイベントを利用

ratechange

学習者がビデオの再生速度を変更した時間

videojsのイベントを利用

何倍速に変更したのかも知りたくて, playbackRate から,再生速度倍率を別途取得

YouTubeを視聴する場合,firstplayの直前に等速処理が入り,その時はユーザが視聴している現在のビデオ再生位置がなく - で記録される.

trackchange

学習者がビデオの字幕を変更した時間

videojsのイベントを利用

どの字幕に変更したのかも知りたくて, 以下のように書いて,字幕切り替え後の言語を別途取得

/* Record subtitle information */
let timeout: number;
player.remoteTextTracks().addEventListener("change", function action() {
   window.clearTimeout(timeout);
   let showing = Array.from(player.remoteTextTracks()).filter(function (
      track
   ) {
      if (track.kind === "subtitles" && track.mode === "showing") {
      return true;
      } else {
      return false;
      }
   })[0];
   timeout = window.setTimeout(function () {
      player.trigger("subtitleChanged", showing);
   }, 10);
});
player.on("subtitleChanged", function (_, track) {
   if (track) {
      sendLog("trackchange", player, track.language);
   } else {
      sendLog("trackchange", player, "off");
   }
});
forward

学習者がビデオの早送りをした時間

videojsの標準機能になかったので,videojs-seek-buttons で機能を実装した.

早送り処理の途中で 'this.options_.direction' の値が forward か back になっていたので,これをイベントとしてログに飛ばす処理を追加した.

後から追加しなくてもいいようにしたい.

( /lti/node_modules/videojs-seek-buttons/dist/videojs-seek-buttons.es.js に追加)

function postForm(req) {
   const form = new FormData();
   Object.entries(req).forEach(([key, value]) => form.append(key, value));
   return {
   method: "POST",
   body: form,
   };
}
const sendLogPath = `/lti//call/log.php`;
const player = this.player_;
const currentSrc = player.currentSrc();
const youtubeQuery = currentSrc.split("?")[1];
const youtubeVideoId =
   new URLSearchParams(youtubeQuery).get("v");
const currentTime = player.currentTime();
const sessionStorageKey = "session";
const res = sessionStorage.getItem(sessionStorageKey);
const session = JSON.parse(res);
const req = {
   event: this.options_.direction,
   detail: "-",
   file: youtubeVideoId,
   query: youtubeQuery,
   current: currentTime.toString(),
   rid: session.lmsResource,
   uid: session.id,
   cid: session.lmsCourse,
   nonce: session.nonce,
};
if(!session.role){
   fetch(sendLogPath, postForm(req));
}
back

学習者がビデオの巻き戻しをした時間

他は forward と同様

beforeunload-ended

学習者がビデオをどこまで視聴したか

ブラウザのイベントを利用

ブラウザを閉じたり,別のウィンドウやタブに切り替えたイベントで目的が達成できそうだったので採用した.

pagehide-ended

beforeunload-endedと同様

unload-ended

beforeunload-endedと同様

hidden-ended

beforeunload-endedと同様

current-time

学習者がビデオをどこまで視聴したか

スクリプトを作成した.

setInterval(function () {
   sendLog("current-time", player);
}, 10000);

ブラウザのイベント利用は不安だったので,現在の再生位置を把握できるように一定周期でログをとったほうが安心かなと思い作成した

現在は,プレイヤーの再生停止の有無に関係なく10秒毎に取得しつづけている

changepage

学習者が学習コンテンツにある,別のマイクロコンテンツに切り替えた時間 (時間とするとフォーマットと例が欲しいです。)

スクリプトを作成した.

どのマイクロコンテンツに変更したのかも知りたくて, マイクロコンテンツのIDを別途取得 (現在のマイクロコンテンツIDなのか、遷移先のマイクロコンテンツIDなのかとか、説明と具体例が必要かと) (現在のchangepageのフォーマットは、変更した時刻+遷移元のマイクロコンテンツIDという理解でいいのでしたっけ)

現在は,自動遷移か手動遷移の区別はついていない.区別が必要かは検討事項とする.

3.2. ログ

3.2.1. ログ出力をする対象者

ログ出力するユーザーの定義

対象

ログ取得?

LTIでの対象判定

補足

管理者

いいえ

roles に administrator
が含まれている
LMSの管理者からの
アクセスを想定

教師

いいえ

roles に administrator が含まれておらず,
instructor が含まれている
LMSの教師やサポーターから
のアクセスを想定

受講者

はい

上記以外

LMSの受講者からのアクセス
を想定

3.2.2. ログの出力先

syslogへ出力する。

本ログの識別子として、固定値で 'videoplayerlog' を出力している。

3.2.3. ログの出力タイミング

イベントのタイミングで、出力する。

なので、学生がVideo視聴している間10sec毎を基本として、各種イベントが起きた時には、下記のログフォーマットで書き出す。

3.2.4. ログのフォーマット

syslogへ、TSV(タブ区切り)で出力する。

フォーマットについては、 ログ出力の仕様を整理・定義する · Issue #18 · npocccties/ChibiCHiLO でも議論しているので、必要に応じて参照すること。

ログフォーマット

No.

項目名

内容例

補足

1

サーバー時間
(年月日)

2000-01-01

2

サーバー時間
(時分秒)

01:01:01

3

サーバー時間
(タイムゾーン)

JST

4

Event

5

Event Value

6

Video名

tnahJxT-td8 (Youtubeの場合)
sample.mp4 (Wowzaの場合)
1084537 (Vimeo は現在未対応)

7

URLパラメーター

v=tnahJxT-td8 (Youtubeの場合)
wowzatokencustomparameter=..(略)..==
(Wowzaの場合)
※未想定 (Vimeo は現在未対応)

8

ユーザが視聴している現在の
ビデオ再生位置

0 や 16.462621 など

9

Client IP Address

xxx.xxx.xxx.xxx

10

ブラウザのUA

下記参照

11

LTIに送られたリソース情報

hoge:1 や huga:1 など

getResourceKey()で取得する値

12

LTIに送られたユーザ情報

hoge:1 や huga:1 など

getUserKey()で取得する値

13

LTIに送られたコース情報

hoge:1 や huga:1 など

getCourseKey()で取得する値

14

LTIに送られたnonce

d8317e3ec7f0d339209d787f9edd78dc

15

マイクロコンテンツ
判定キーワード

videoplayerlog

固定

16

video種別

youtube
vimeo
wowza

17

視聴ページ

/book
/bookmarks

18

トピックID

1
将来追加予定

19

ブックID

1
将来追加予定

20

再生速度

0.5 や 1 など
将来追加予定
サーバ時間(年月日)

前システムから踏襲する。YYYY-MM-DD のformatで、0を前置する.

サーバ時間(時分秒)

前システムから踏襲する。HH:MM:SS のformat で、0を前置する。

サーバー時間(タイムゾーン)

前システムから踏襲する。2021-01-13の段階では、JST固定です。次期ログ開発で改修予定です。

イベント

イベントについては、 視聴ログ出力機能 を参照せよ。

イベント値

イベント値について、 視聴ログ出力機能 に書いていないので、ここか、イベントの方で定義する。これって、「イベントとは別に取得した情報」のことを指していますか? 時間の場合は,ユーザが視聴している現在のビデオ再生位置と同じ値(0 や 16.462621 など)でコンマ秒まで出る.

Video名

Videoを識別する固有の文字列です。YouTubeはビデオIDになります。 wowzaだと動画のファイル名. vimeo は未対応だが,対応するならvimeoのビデオIDを想定している。

URLパラメータ

URLのパラメータを記述する。YouTube, vimeo, wowza で入るものが異なる。 `この辺の議論を参照せよ。<https://github.com/npocccties/ChibiCHiLO/issues/18#issuecomment-758419047>`_

ビデオ再生位置

これは、暗黙の了解として「ユーザが視聴している現在のビデオ再生位置」というのが正確な表現になります。 位置の単位はコンマ秒含む秒単位(0 や16.462621 など)最大値はTypeScript,JavaScriptにて表現できる範囲でお願いします。

Client IP Address

ご指摘がありました。しかし、結論からいうと現在のログはIPv4だけで良いです。次期ログ開発時に、IPv6のアドレスが必要かを議論します。see also 2021-01-19 ミーティングで確認する内容 · Issue #16 · npocccties/ChibiCHiLO-private を確認する。それまでは、IPv4のみの環境で、フォーマットは既存と同じ、xxx.xxx.xxx.xxx の十進数を.(dot)で区切った数値になります。

ブラウザのUA

使っているブラウザのユーザエージェントをそのまま記録する。ユーザが設定しているUAをそのまま記録する。

LTIに送られたリソース情報

table利用している連携データ を参照してください。Learning Tools Interoperability | IMS Global Learning Consortium によると、Learning Platform (Moodle/blackboard) から、Learning Tool (Chibi-CHiLO) に、LTIプロトロルを用いてデータを送ってくるので、LTIに送るというのはちょっと違和感があります。あと、lti:1 getUserKey() で取得する値というのは、table利用している連携データ の中身について話だと理解している。しかしどのように利用しているのか。具体的なデータは何かを以下のコース情報、リソース情報に書いてあげる必要がある。 LMSから送られる~に名称を変更したほうがいいか? lti:1 のコロン(:)より前は,LMSから送られる oauth_consumer_key が入る.LTI_keyがhogeなら,hoge:1 となる 利用している連携データは resource_link_id

LTIに送られたユーザ情報

同上 利用している連携データは user_id

LTIに送られたコース情報

同上 利用している連携データは context_id

LTIに送られたnonce

nonceはLTIサーバにLMSから情報を送るたびに変化する一意の文字列です. 同じユーザが同じコースにあるリソースから来たとしても,変化するので,視聴しなおしたかどうか.判断回数などが分かるようになる moodleは"6f9beca26ec542e84c71931ad1276137",backboardは"352126796144595" のように桁数も使用する文字の種類も違うようだが,LTIに情報を送るたびに変化するという仕様は同じ.

マイクロコンテンツ判定キーワード

本ログの識別子として、videoplayer関係のログであることを示す。固定値

video種別

YouTube,vimeo,wowza の区別が付くように種別を入れる。

視聴URLのパス

/book: 通常の視聴,/bookmarks: タグ管理画面での視聴区別

3.2.5. ログの整形

syslog出力時、ログのフォーマット以外の不要な情報が含まれている場合があるのでバッチ等で別途除去する。

例:シェルスクリプトで前日のログを整形する

( cron )

30 3 * * * /var/log/chibichilo/log.sh

( /var/log/chibichilo/log.sh )

#!/bin/sh

LOGA=/var/log/chibichilo/log_`date --date '1 day ago' +%Y%m%d`.log
LOGB=/var/log/chibichilo_parse/log_`date --date '1 day ago' +%Y%m%d`.log

sed -e 's/^.*php.*: //g' -e 's/^.*httpd.*: //g' -e 's/^.*www.*: //g' -e 's/^.*line=//g' -e 's/#012/\n/g' -e 's/#011/      /g'  -e 's/\\x09/       /g'  -e '/current-time  -       -       /d' -e 's@https://youtu.be/@@g' -e 's/::ffff://g' $LOGA > $LOGB