YouTube 怎麼禁止手機使用者背景播放的?透過 Chrome 開發者工具 深度尋訪 YouTube 的前端程式碼

你是不是也有相同的經驗,同樣是 Safari , 明明 Pornhub、XVIDEOS 常常不小心背景播放,但是為什麼控制中心按下播放時 YouTube 就是不能播放呢?讓我們透過 Chrome DevTool 一虧他們的程式碼邏輯吧!

當你背景播放 YouTube 時…

afari 背景播放理論上是要可以 work 的,但是在 YouTube & YouTube Music,卻好像失靈了?讓我們用幾分鐘搞懂 YouTube 到底做了什麼吧!

#反正 YouTube 沒有在台灣招募前端工程師系列

在桌面環境重現這個 Feature

  1. 進入 YouTube 影片
  2. 按下 F12 ,選擇一個手機的按鈕
  3. 重新整理至手機版 YouTube 頁面
  4. 按下播放並切換分頁
你可以簡單地在電腦上重現 YouTube 的“禁止背景播放“功能

YouTube 怎麼知道我不在頁面 / Safari 在背景呢?

首先這是一個網頁,YouTube 的 Front-end Engineer 也不是用魔法在寫 code ,所以勢必是透過 JavaScript 、 Browser 的 API 所賦予的能力,所以只會有以下可能:

  • 聽事件,頁面是不是 blur 了?
  • 聽事件,頁面的 visibility 是不是有改變?

都是 聽事件 ,讓我們 師以長技以制夷 [註] 用 Chrome 來看看 YouTube 在監聽了啥事件吧!

註:YouTube 跟 Chrome 都是同一家公司,如果你不相信的話可以 Google 一下喔!

記得 Ancestor 要勾起來喔

我們可以基本確定,禁止背景播放的方法大概率的是來自於這四個 callback 其中之一。

讓我們一個一個按下 Remove

好,我找到了!你呢?

正是第三個 callback (base.js:5291),remove 後切換分頁都會繼續播放了!

讓我們看看 base.js:5291 做了什麼吧

首先別忘了 Chrome 有個按鈕可以 Beautify 壓過的 Code,並且在行數的部分點選設定斷點

然後讓我們來觸發這個斷點吧!

在斷點的階段你可以hover在變數上面觀察變數的值,或是 hover在 function 上面跟進函數位置,進而下另一個斷點。

移動到別的分頁

a===3, this.A===0 將會觸發 this.K 並且傳入 visibilitystatechange

從別的分頁移動回 YouTube

this.a === 0 不會觸發 visibility(‘visibilitystatechange’)

我們可以看到這段 Code 如下:

function() {
this.K("visibilitychange");
var a = this.getVisibilityState(this.i, this.isFullscreen(), this.g, this.isInline(), this.o);
a != this.A && this.K("visibilitystatechange");
this.A = a
}

有了上述的前提,我們可以發現 有執行 this.K('visibilitystatechange') 勢必會把影片暫停,所以 3 這個東西只要繞過去即可。而 0 則是恢復 visible (我們不 care 這個 case)。

回傳數字的關鍵函數是 this.getVisibilityState ,讓我們跟進去看看裡面在做什麼。

function(a, b, c, d, e) {
return a ? 4 : sQ() ? 3 : b ? 2 : c ? 1 : d ? 5 : e ? 7 : 0
}

他會依照 a sQ() b c d e 的順序去判斷何者先為 true

讓我們繼續跟進 sQ 函數

各位前端工程師應該不陌生吧

答案揭曉 — YouTube 透過監聽 visibility change 的事件

並在監聽函數中,檢查 document.visibilityState 來判定這個 change event 是 離開分頁 還是 進入分頁

該原始程式碼應該如下:

document.addEventListener('visibilitychange',()=>{
if(document.visibilityState==='visible'){
// 進入分頁
}else{
// 離開分頁
// 暫停 video
}
})

Make sense!Make sense!跟一開始猜的一樣。

確定是哪個事件與狀態決定行為就夠了!

再繼續追下去就沒有任何意義了,剩下的就是 uglify 的變數罷了。

讓我們來談談如何繞過 (bypass) 吧!

為什麼我們說追到事件的根源就夠了?

我們如果是要繞過其不去執行,最快的做法就是從事件攔截掉 [2]。

註2: 由於某些事件攔截會導致應用異常,某些操作要繞過的話,避無可避地需要更精確的做法 & 完整逆向分析 ,並且從外部修改裡頭的狀態,這個我們之後的文章再討論。

讓我們攔截 visibilitychange 吧!關於事件的介紹與基本知識可以參考另外一篇文章 — 事件 (Event) 的註冊、觸發與傳遞

原則上事件都可以透過 e.stopPropagation() 來避免向下傳遞,常見的攔截事件方法往往是:透過埋入捕獲階段 + stopPropagation() = 繞過 bubble handler

不過 visibilitychange 這個事件比較怪一些,我們需要透過另外一個更強硬的做法 — stopImmediatePropagation() ,讓我們來試試看吧。

window.addEventListener('visibilitychange', e=>e.stopImmediatePropagation(), true);

你可以透過上述程式碼在 console 體驗其效果。

後記

iPhone 有一個方法(Shortcut)可以在 Safari 頁面上插入自己的 JavaScript,下次有機會再寫一篇文章介紹它。

如果你早就熟係 iOS shortcut,那麼你或許會對這個專案有興趣:realdennis/shortcuts-mono

免責聲明

本文透過現有的工具及使用者可見的程式碼做分析,無任何資訊、產權破壞、重製,此文僅做學習之用。所有的程式碼皆為原瀏覽器提供的功能,無任何惡意代碼與應用破壞之行為,若使用者用於非法用途,與此文章及該作者無關。

If any interest, 👉 https://realdennis.me.

If any interest, 👉 https://realdennis.me.