「動画+コマーシャル」の配信方式を調査

最近のコマーシャルの挿入仕様に変化があった事は、最近のページで書きました。

 

 

この変更に一応の対策をしましたが、その後のチェックで「補助ミュート機能」が動作しない場合がたまに生じる事を確認しました。 今回は、この漏れの問題の機序について、もう少し調べました。

 

これまでに判っている事は、「動画コンテンツ」「CM」を同じひとつの「src」で配信する方式が使われ始めたという事です。

 

 

 

 

この場合の「src」は、一般のリンクとは全く異なった意味を持っています。

 

src="blob:https://abema.tv/a1f387f2-1c16……………"

 

「blob」が先頭に付いたアドレスに関しては、以下が少し参考になります。

 

 

「src」は、その動画ページ内の内部的な参照を指していて、実際の動画データの場所とは異なるという事だそうです。 下は、この処理の模式図です。

 

 

 

「blob:https://~」という「src」の形式は、「 AmbTV OnAir」の制作当初から変わっていません。 という事は、この「src」が決まる前段階の処理が変更され、以前は複数の「src」をやりくりして「動画コンテンツ」⇄「CM」の切換えていた方式が、表面上はひとつの「src」になったと考えられます。

 

 

 

「src」を常時表示して調査 

「video要素」の「src」は、以前の様に「CM」挿入時に変わらなくなったのですが、チャンネルを切換えると変わります。 テレビチャンネル形式の動画コンテンツは、短いと30分程度、長いと2~6時間といったものが多く、「CM」で分断されなくなった現在、いったいどの程度の時間で「src」が切換わるのか気になります。

 

そこで、調査のために以下のテストコードを作って、最近の「 AmbTV OnAir」のバージョンに組み込みました。

 

let disp=
    '<div class="tw_monitor">'+
    '<span class="tw_ye">🟡</span>'+
    '<span class="tw_gr">🟢</span>'+
    '  <span class="tp_disp"></span>'+
    '</div>'+
    '<style>.tw_monitor { position: fixed; top: 20px; left: 200px; '+
    'padding: 10px; font: 16px Meiryo; color: #000; z-index: 1000; } '+
    '.tw_ye, .tw_gr { opacity: 0; }'+
    '.tp_disp { color: #fff; }</style>';
if(!document.querySelector('.tw_monitor')){
    document.body.insertAdjacentHTML('beforeend', disp); }

function yellow_ck(){ // 🟡
    let tw_ye=document.querySelector('.tw_ye');
    if(tw_ye){
        tw_ye.style.opacity='1';
        setTimeout(()=>{
            tw_ye.style.opacity='0'; }, 2000); }}

function green_ck(){ // 🟢
    let tw_gr=document.querySelector('.tw_gr');
    if(tw_gr){
        tw_gr.style.opacity='1';
        setTimeout(()=>{
            tw_gr.style.opacity='0'; }, 400); }}

function disp_src(){
    let tp_video=TP.querySelector('video[src]');
    if(tp_video){
        let tp_src=tp_video.src.split('/')[3];
        let tp_disp=document.querySelector('.tp_disp');
        if(tp_disp){
            tp_disp.textContent=tp_src; }}}

 

最後の「disp_src()」は、動画コンテンツ(この場合はCMも含む)の「src」を調べて、その固有値の下の太字の部分を抜き出して表示します。 この関数を、4secごとに実行する様にしました。

 

src="blob:https://abema.tv/a1f387f2-1c16……………"

 

 

 

調査の結果 

下は、あるチャンネルの「動画コンテンツ」の表示中の「src」です。

 

 

 

このチャンネルが「CM」表示に切換った状態です。

 

 

 

この「CM」の表示に際して、「src」は切換わりません。

 

 

 

「CM」から再び「動画コンテンツ」に切換わっても、この「src」は不変です。

 

しかし、隣のチャンネルに切換えて元のチャンネルに戻ると、先と異なった「src」になります。 これは、動画データを内部で生成する部分が更新され、参照先の「src」がリセットされるためと思われます。

 

 

 

 

ちなみに、複数のウインドウで同じチャンネルを表示した場合は、ウインドウごとに「src」が異なります。 また、「CM」の内容は同一ではなく、差し換えは動画プレヤーごとに行われるのでしょう。

 

 

「src」の持続時間

ひとつのチャンネルを選択して開くと「src」が決定されますが、次にチャンネルを変更するまで、何時間経っても「src」は変わらない様です。 当初は、長い「動画コンテンツ」の放映後は切換えがありそうに思っていましたが、「動画コンテンツ」や「CM」がどの様に切換わっても、「src」は変わらない事が判りました。

 

 

「CM」のコンテンツの判別 

これは新たな問題が潜んでいました。「 AmbTV OnAir」ver.1.2 以降に導入したキャプチャ操作でのCM判定手法は、「一定範囲でのみ有効」という事を確認しました。

 

同じチャンネルでそれまでに「動画コンテンツ」が再生されていた場合は、その段階でコピーガードの環境が設定されるらしく、「CM」に切換った後もコピーガードが働きます。 そのため、キャプチャ操作による「CM」判定が不能となります。

 

ただし、チャンネルを切り替えた先が「CM」だった場合は、コピーガード環境が未設定の状態らしく、キャプチャ操作による「CM」判定が可能です。 しかし「動画コンテンツ」に切換わった後は判定不能になります。

 

以上から、現在の「 AmbTV OnAir」のミュート機能は以下に纏められます。

 

◎「 AmbTV OnAir」の「ミュート機能」は「Abemaアニメーションロゴ」で「CM」を判定するので、多くの場合はこれまでの通り機能します。

 

◎「Abemaアニメーションロゴ」を表示しない「CM」切換えに対応する「補助ミュート機能」は、機能できなくなりました。

 

◎ チャンネル切換え先が「CM」の場合と、「CM」しか再生されない「CMチャンネル」では、「補助ミュート機能」は機能します。

 

 

 

「動画+コマーシャル」の配信方式に対応策なし 

「AbemaTV」のコピーガード強化や「動画コンテンツ+CM」の「src」の相乗りは、「 AmbTV OnAir」にとっては不利な条件が増えました。「Abemaアニメーションロゴ」を表示しない「CM」には対応できないので、時々「CM」がミュートなしで表示される事が生じます。

 

対策できないのは残念ですが、「コマーシャル」の挿入仕様の観測を今後も行う予定です。

 

 

 

画面の「左Click」でミュートのマニュアル適用 

動画の画面を「Ctrl+左Click」すると、「ミュート」の「設定パネル」が表示され、同時に「ミュート」を仮適用した状態に出来ます。 これは「ミュート」の状態が判り易くするための仮適用です。

 

一方、このページで調べた様に、「補助ミュート」が効かない「CM移行」が時々発生するので、動画の画面の「左Click」で「ミュート」の適用・解除が出来る様にしました。「TVチャンネル型の動画プレーヤ」は、本来は画面のクリックに全く反応しないので、このマニュアル適用は有効な裏技と思います。

 

●「コマーシャル」がミュートされない場合は動画画面を「左Click

 

という操作は、直ぐに慣れる事ができると思います。「動画コンテンツ」へ移行すると自動的にミュートは解除されるので、その後は何もする必要がありません。 また、この機能は、ミュートが適用されたCMの内容が気になる時に、ミュート解除をする事にも使えます。 やはりその後に何もしなくても、通常の運転に戻ります。

 

 

 

「 AmbTV OnAir」の操作マニュアル

このツールは「 AmbTV Comfy」のサブツールで、両方を同時に併用できます。

「ミュート」機能の設定方法は、以下のページを参照ください。

 

 

 

 

「 AmbTV OnAir」を利用するには

このツールは Chrome / Edge / Firefox版の拡張機能「Tampermonkey」上で動作します。 以下に、このツールの導入手順を簡単に説明します。

 

❶「Tampermonkey」を導入します

◎ 使用しているブラウザに拡張機能「Tampermonkey」を導入する事が必要です。

既に「Tampermonkey」を導入している場合は、この手順 ❶ は不要です。 

拡張機能の導入については、以下のページに簡単な説明があるので参照ください。

 

 

❷「Tampermonkey」にスクリプトを登録します

◎「Tampermonkey」の「」マークの「新規スクリプト」タブを開きます。

 

 

 

◎「新規スクリプト」には、最初からテンプレートが記入されています。 これは全て削除して、完全に空白の編集枠に 下のコードをコピー&ペーストします。

 

〔コピー方法〕 軽量シンプルなツール「PreBox Button   」を使うと

  コード枠内を「Ctrl+左Click」➔「Copy code 」を「左Click」

  の操作で、掲載コードのコピーが可能になります。

 

◎ 最後に「ファイル」メニューの「保存」を押すと、ツールが使用可能になります。

 

 

〔  AmbTV OnAir 〕 ver. 1.5

 

// ==UserScript==
// @name         AmbTV OnAir
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  AbemaTV ユーティリティ
// @author       Ameba User
// @match       https://abema.tv/*
// @icon          https://www.google.com/s2/favicons?sz=64&domain=abema.tv
// @noframes
// @grant        none
// ==/UserScript==


let oa_mute;
let oa_size;
let oa_opac;

let target=document.querySelector('head > title');
let monitor0=new MutationObserver(tv_player_env);
monitor0.observe(target, { childList: true });

tv_player_env();

function tv_player_env(){
    let retry=0;
    let interval=setInterval(wait_target, 20);
    function wait_target(){
        retry++;
        if(retry>100){ // リトライ制限 100回 2secまで
            clearInterval(interval); }
        let TP=document.querySelector('.com-tv-TVScreen__player');
        if(TP){
            clearInterval(interval);
            player_vol(TP); }}

} // tv_player_env()



function player_vol(TP){
    let monitor1=new MutationObserver(con_vol);
    monitor1.observe( TP, { childList: true });

    con_vol();

    function con_vol(){ // ABEMAロゴによるミュート
        let LF=document.querySelector('.com-tv-LinearFooter__feed-super');
        if(TP.querySelector('.com-tv-TVScreen__eyecatch')){
            if(LF.textContent){
                v_vol(1); }}
        else{
            if(!LF.textContent){
                v_vol(0); }}}



    let LF=document.querySelector('.com-tv-LinearFooter__feed-super');
    let monitor2=new MutationObserver(con_vol2);
    monitor2.observe( LF, { childList: true });

    con_vol2();

    function con_vol2(){ // 動画タイトルによるミュート
        if(LF.textContent){
            v_vol(1); }
        else{
            setTimeout(()=>{
                ad_check();
            }, 200); }}


    function ad_check(){
        let cvs=
            '<canvas id="cvs" style="position: fixed; z-index: -1; visibility: hidden;">'+
            '</canvas>';
        if(!document.querySelector('#cvs')){
            document.body.insertAdjacentHTML('beforeend', cvs); }
        let canvas=document.querySelector('#cvs');


        let retry_s=0;
        let interval_s=setInterval(cv_check, 1000);
        function cv_check(){
            retry_s++
            if(retry_s>15){ // リトライ制限 15sec
                clearInterval(interval_s); }
            capture(canvas);


            function capture(canvas){
                let video=
                    document.querySelector('.com-a-Video__video video[src]');
                if(video){
                    canvas.width=1;
                    canvas.height=1;
                    canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
                    if(canvas.getContext('2d')){
                        let imageData=canvas.getContext('2d').getImageData(0, 0, 1, 1);
                        let data=imageData.data;

                        if(data[0]+data[1]+data[2]!=0){
                            v_vol(0);
                            retry_s=16; }}}} // capture()

        } // cv_check()

    } // ad_check()



    setTimeout(()=>{
        let LCLI=document.querySelector('.com-tv-LinearChannelListItem--active a');
        if(LCLI){
            let monitor3=new MutationObserver(con_vol3);
            monitor3.observe( LCLI, { attributes: true });

            con_vol3();

            function con_vol3(){
                v_vol(1);
                con_vol2(); }}
    }, 200 );



    setTimeout(()=>{
        let side=document.querySelector('.com-tv-FeedSidePanel__close-button');
        if(side){
            side.click(); }
    }, 600);

    setTimeout(()=>{
        let HM=document.querySelector('.com-m-HeaderMenu');
        let SNc=document.querySelector('.c-application-SideNavigation--collapsed');
        if(HM && !SNc){
            HM.click(); }
    }, 700);


    check_cookie();
    cm_setting();

} // player_vol(TP)



function v_vol(n){ // 0: ミュート 1: 通常
    oa_mute=get_cookie('oa_mute');
    let button=document.querySelector('.com-playback-Volume__icon-button');
    if(button){
        let label=button.getAttribute('aria-label');
        if(n==0 && label=='音声をオフにする'){
            if(oa_mute==0){
                button.click(); }}
        else if(n==1 && label=='音声をオンにする'){
            button.click(); }

        setTimeout(()=>{
            let label_=button.getAttribute('aria-label');
            if(label_=='音声をオフにする'){ // 音声ON
                view(1); }
            else{
                view(0); }
        }, 20);
    } // button

} // v_vol()



function view(n){ // 0: ミュート  1: 通常
    oa_opac=get_cookie('oa_opac');
    oa_size=get_cookie('oa_size');
    let TVS=document.querySelector('.com-tv-TVScreen__player-container');
    if(TVS){
        if(n==0){
            TVS.style.transition='opacity .5s, transform .5s';
            if(oa_opac==0){
                TVS.style.opacity='0.5'; }
            else if(oa_opac==1){
                TVS.style.opacity='0'; }
            else{
                TVS.style.opacity=''; }
            if(oa_size==0){
                TVS.style.transform='scale(0.5)'; }
            else{
                TVS.style.transform=''; }}
        else{
            TVS.style.transition='';
            TVS.style.opacity='';
            TVS.style.transform=''; }}

} //  view()



function get_cookie(name){
    let cookie_req=document.cookie.split('; ').find(row=>row.startsWith(name));
    if(cookie_req){
        if(cookie_req.split('=')[1]==null){
            return 0; }
        else{
            return cookie_req.split('=')[1]; }}
    if(!cookie_req){
        return 0; }}



function check_cookie(){
    oa_mute=get_cookie('oa_mute');
    if(oa_mute!=1){
        oa_mute=0; }
    document.cookie='oa_mute='+oa_mute+'; path=/; Max-Age=2592000';

    oa_size=get_cookie('oa_size');
    if(oa_size!=1){
        oa_size=0; }
    document.cookie='oa_size='+oa_size+'; path=/; Max-Age=2592000';

    oa_opac=get_cookie('oa_opac');
    if(oa_opac!=0 && oa_opac!=1 && oa_opac!=2){
        oa_opac=0; }
    document.cookie='oa_opac='+oa_opac+'; path=/; Max-Age=2592000';

} // check_cookie()



function cm_setting(){
    let help_url="https://ameblo.jp/personwritep/entry-12856628930.html";

    let help_SVG=
        '<svg class="oa_help" width="20"  height="20" viewBox="0 0 150 150">'+
        '<path  fill="#fff" d="M66 13C56 15 47 18 39 24C-12 60 18 146 82 137C92 '+
        '135 102 131 110 126C162 90 128 4 66 13M68 25C131 17 145 117 81 '+
        '125C16 133 3 34 68 25M69 40C61 41 39 58 58 61C66 63 73 47 82 57C84 '+
        '60 83 62 81 65C77 70 52 90 76 89C82 89 82 84 86 81C92 76 98 74 100 66'+
        'C105 48 84 37 69 40M70 94C58 99 66 118 78 112C90 107 82 89 70 94z">'+
        '</path></svg>';

    let TVS=document.querySelector('.com-tv-TVScreen__player-container');
    if(TVS){
        TVS.onclick=function(event){
            if(event.ctrlKey){
                let panel=
                    '<div id="amboa">'+
                    '<div id="oa_head">   CMのミュート設定    '+
                    '<a href="'+ help_url +'" rel="noopener noreferrer" target="_blank">'+
                    help_SVG +'</a> '+
                    '<input type="button" id="oa_close" value="×"></div>'+
                    '<div class="oa_p">ミュート機能の有効 / 無効</div>'+
                    '<div> ミュート機能(音声): '+
                    '<input name="mute" type="radio" id="m0">有効 '+
                    '<input name="mute" type="radio" id="m1">無効'+
                    '</div>'+
                    '<div class="oa_p">ミュート時の画面の設定</div>'+
                    '<div> 画面サイズ: '+
                    '<input name="size" type="radio" id="s0">縮小 '+
                    '<input name="size" type="radio" id="s1">通常'+
                    '</div>'+
                    '<div> 画面の明度: '+
                    '<input name="opacity" type="radio" id="o0">0% '+
                    '<input name="opacity" type="radio" id="o1">50% '+
                    '<input name="opacity" type="radio" id="o2">100%'+
                    '</div>'+

                    '<style>#amboa { position: fixed; top: 60px; left: calc(50% - 190px); '+
                    'font: 16px/24px Meiryo; color: #000; padding: 16px 16px 8px; width: 380px; '+
                    'border: 1px solid #aaa; border-radius: 6px; background: #fff; z-index: 100; } '+
                    '#oa_head { margin: 0 0 15px; padding: 5px 15px 3px; '+
                    'font-weight: bold; color: #fff; background: #2196f3; text-align: center; } '+
                    '.oa_help { vertical-align: -5px; } '+
                    '#oa_close { padding: 0 2px; height: 20px; line-height: 16px; } '+
                    '.oa_p { padding: 2px 8px 0; margin: 8px 0 4px; border: 1px solid #aaa; '+
                    'line-height: 22px; } '+
                    'input[type="radio"]{ margin: 0 .2em; }'+
                    '</style>'+
                    '</div>';
                if(!document.querySelector('#amboa')){
                    document.body.insertAdjacentHTML('beforeend', panel); }

                live_mute();
                set_radio();

                function set_radio(){
                    oa_mute=get_cookie('oa_mute');
                    let m0=document.querySelector('#m0');
                    let m1=document.querySelector('#m1');
                    if(oa_mute==0){
                        m0.checked=true;
                        mute(0); }
                    else{
                        oa_mute=1;
                        m1.checked=true;
                        mute(1); }
                    document.cookie='oa_mute='+oa_mute+'; path=/; Max-Age=2592000';

                    m0.onchange=function(){
                        mute(0);
                        document.cookie='oa_mute=0; path=/; Max-Age=2592000';
                        live_mute(); }

                    m1.onchange=function(){
                        mute(1);
                        document.cookie='oa_mute=1; path=/; Max-Age=2592000';
                        live_mute(); }


                    oa_size=get_cookie('oa_size');
                    let s0=document.querySelector('#s0');
                    let s1=document.querySelector('#s1');
                    if(oa_size==0){
                        s0.checked=true; }
                    else{
                        oa_size=1;
                        s1.checked=true; }
                    document.cookie='oa_size='+oa_size+'; path=/; Max-Age=2592000';

                    s0.onchange=function(){
                        document.cookie='oa_size=0; path=/; Max-Age=2592000';
                        live_mute(); }

                    s1.onchange=function(){
                        document.cookie='oa_size=1; path=/; Max-Age=2592000';
                        live_mute(); }


                    oa_opac=get_cookie('oa_opac');
                    let o0=document.querySelector('#o0');
                    let o1=document.querySelector('#o1');
                    let o2=document.querySelector('#o2');
                    if(oa_opac==0){
                        o1.checked=true; }
                    else if(oa_opac==1){
                        o0.checked=true; }
                    else{
                        oa_opac=2;
                        o2.checked=true; }
                    document.cookie='oa_opac='+oa_opac+'; path=/; Max-Age=2592000';

                    o0.onchange=function(){
                        document.cookie='oa_opac=1; path=/; Max-Age=2592000';
                        live_mute(); }

                    o1.onchange=function(){
                        document.cookie='oa_opac=0; path=/; Max-Age=2592000';
                        live_mute(); }

                    o2.onchange=function(){
                        document.cookie='oa_opac=2; path=/; Max-Age=2592000';
                        live_mute(); }


                    function mute(n){
                        let s0=document.querySelector('#s0');
                        let s1=document.querySelector('#s1');
                        let o0=document.querySelector('#o0');
                        let o1=document.querySelector('#o1');
                        let o2=document.querySelector('#o2');
                        if(n==0){
                            s0.disabled=false;
                            s1.disabled=false;
                            o0.disabled=false;
                            o1.disabled=false;
                            o2.disabled=false; }
                        else{
                            s0.disabled=true;
                            s1.disabled=true;
                            o0.disabled=true;
                            o1.disabled=true;
                            o2.disabled=true; }}

                } // set_radio()


                let amboa=document.querySelector('#amboa');
                let oa_close=document.querySelector('#oa_close');
                if(amboa && oa_close){
                    oa_close.onclick=function(event){
                        event.preventDefault();
                        amboa.remove(); }}}

            else{
                let button=document.querySelector('.com-playback-Volume__icon-button');
                if(button){
                    let label=button.getAttribute('aria-label');
                    if(label=='音声をオフにする'){
                        v_vol(0); }
                    else{
                        v_vol(1); }}}

        }} // if(TVS)

} // cm_setting()



function live_mute(){
    let LF=document.querySelector('.com-tv-LinearFooter__feed-super');
    if(!LF.textContent){
        setTimeout(()=>{
            oa_mute=get_cookie('oa_mute');
            v_vol(oa_mute);
        }, 200); }}







 

 

 

「AmbTV OnAir」最新版について 

旧いバージョンの JavaScriptツールは、アメーバのページ構成の変更で動作しない場合があり、導入する場合は最新バージョンをお勧めします。

 

●「AmbTV OnAir 」の最新バージョンへのリンクは、以下のページのリンクリストから探せます。