PageSourceSearch

https://www.jiann-life.com/assets/frontend/javascript/plugins/gtut_youtube_player_text08.js

js jiann-life.com collected 2026-10-08 16:30:16 UTC 8,554 bytes, 239 lines download raw bytes

1class GtutYoutubeText08Player {
2    constructor(selector, videoIframe, config) {
3
4        this.videoIframe = videoIframe
5        this.sectionEl = selector; // selector 變成實際的 DOM 元素
6        this.videoId = this.sectionEl.getAttribute('data-video-id');
7        this.overlay = this.sectionEl.querySelector('.version_text_yt_overlay');
8        this.player = null; // 在類別中定義播放器成員變量
9        this.youtubeSettings = {
10            videoId: this.transformVideoId(this.videoId),
11            height: '1440',
12            width: '2560',
13            useCoverImg: true,
14            autoPlay: true,
15            loop: true,
16            bleed: false,
17            playerVars: {
18                controls: 0,
19                fs: 0,
20                iv_load_policy: 3,
21                rel: 0,
22                modestbranding: 1,
23                playsinline: 1,
24            },
25            events: {
26                onReady: this.onPlayerReady,
27                onStateChange: this.onPlayerStateChange,
28                onError: this.onPlayerError, // 新增錯誤處理事件
29            },
30        };
31        this.firstRePlayVideo = true;
32        this.firstInThisSite = sessionStorage.getItem("inThisSite_text08");
33        if (this.firstInThisSite) {
34            this.firstRePlayVideo = false;
35        } else {
36            sessionStorage.setItem("inThisSite_text08", true);
37        }
38        this.extendConfig = this.extendConfig.bind(this);
39        this.addCoverImg = this.addCoverImg.bind(this);
40        this.removerCoverImg = this.removerCoverImg.bind(this);
41        this.checkLoopShowControls = this.checkLoopShowControls.bind(this);
42        // Call initialization function
43        this.playerInitialized = false;
44        this.apiLoaded = false;
45        this.init(config);
46    }
47    init(config) {
48        this.youtubeSettings = this.extendConfig(this.youtubeSettings, config);
49        if (!this.youtubeSettings.loop) {
50            this.youtubeSettings.playerVars.controls = 1;
51        }
52        if (!this.apiLoaded) {
53            // 加載 YT API
54            let existingScript = document.querySelector('script[src="' + 'https://www.youtube.com/iframe_api' + '"]');
55            if (!existingScript) {
56                const tag = document.createElement('script');
57                tag.src = 'https://www.youtube.com/iframe_api';
58                const firstScriptTag = document.getElementsByTagName('script')[0];
59                firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
60            }
61
62            // 标记 API 已加载
63            this.apiLoaded = true;
64        }
65            // 等待 YT API 加載完成
66            if (window.onYouTubeIframeAPIReady) {
67                const oldCallback = window.onYouTubeIframeAPIReady;
68                window.onYouTubeIframeAPIReady = () => {
69                    oldCallback();
70                    this.initPlayer();
71                    // console.log('08yt api ready');
72                }
73
74                this.initPlayer();
75            } else {
76                window.onYouTubeIframeAPIReady = () => {
77                    this.initPlayer();
78                }
79            }
80
81        // console.log('init08');
82
83
84    }
85
86    initPlayer() {
87
88            // console.log('initPlayer8');
89            if (!this.playerInitialized) {
90                const hiddenProperty = 'hidden' in document
91                ? 'hidden'
92                : 'webkitHidden' in document
93                    ? 'webkitHidden'
94                    : 'mozHidden' in document
95                        ? 'mozHidden'
96                        : null;
97            const visibilityChangeEvent = hiddenProperty.replace(/hidden/i, 'visibilitychange');
98            let leaveTime;
99            let backTime;
100            const onVisibilityChange = () => {
101                if (!document[hiddenProperty]) {
102                    backTime = Date.now();
103                    if (leaveTime == undefined || backTime > leaveTime + 600000) {
104                        location.reload();
105                    }
106                } else {
107                    leaveTime = Date.now();
108                }
109            };
110                document.addEventListener(visibilityChangeEvent, onVisibilityChange);
111            const that = this
112            function checkYTPlayer() {
113                if (typeof YT !== 'undefined') {
114                    // YT exists, create the player
115                    that.player = new YT.Player(`${that.videoIframe}`, that.youtubeSettings);
116                } else {
117                    // YT does not exist yet, check again after 200 milliseconds
118                    setTimeout(checkYTPlayer, 300);
119                }
120            }
121
122            // 開始檢查 YT Player
123            checkYTPlayer();
124
125            if (this.youtubeSettings.useCoverImg) {
126                this.addCoverImg(this.youtubeSettings.useCoverImg);
127            }
128                this.playerInitialized = true;
129
130                // console.log('playerInitialized08');
131        }
132    }
133
134    transformVideoId(id) {
135        const youtubeVideoIdLength = 11;
136        let newId = '';
137        if (id.length <= youtubeVideoIdLength) {
138            return id;
139        }
140        if (id.indexOf('watch?v=') > 0) {
141            newId = id.substr(id.indexOf('watch?v=') + 8, youtubeVideoIdLength);
142        } else if (id.indexOf('youtu.be/') > 0) {
143            newId = id.substr(id.indexOf('youtu.be/') + 9, youtubeVideoIdLength);
144        } else if (id.indexOf('/embed/') > 0) {
145            newId = id.substr(id.indexOf('/embed/') + 7, youtubeVideoIdLength);
146        } else {
147            newId = id.substr(id.lastIndexOf('/') + 1, id.length);
148            if (newId.length > youtubeVideoIdLength) {
149                newId = newId.substr(0, youtubeVideoIdLength);
150            }
151        }
152        return newId;
153    }
154
155    addCoverImg(src) {
156        let imgUrl;
157        if (src === true) {
158            imgUrl = `https://img.youtube.com/vi/${this.youtubeSettings.videoId}/maxresdefault.jpg`;
159            const customCoverImg = this.overlay.getAttribute('data-coverimg');
160            if (customCoverImg) {
161                imgUrl = customCoverImg;
162            }
163        } else {
164            imgUrl = src;
165        }
166
167        this.overlay.style.backgroundImage = `url(${imgUrl})`;
168    }
169
170    removerCoverImg() {
171        this.overlay.style.opacity = 0;
172    }
173
174    extendConfig(a, b) {
175        for (const key in b) if (b.hasOwnProperty(key)) a[key] = b[key];
176        return a;
177    }
178
179    checkLoopShowControls= () => {
180        if (!this.youtubeSettings.loop) {
181            this.overlay.style.pointerEvents = 'none';
182        }
183    }
184
185    // 顯示封面圖的輔助方法
186    showFallbackCover() {
187        // 確保封面圖顯示
188        if (this.youtubeSettings.useCoverImg) {
189            this.addCoverImg(this.youtubeSettings.useCoverImg);
190            this.overlay.style.opacity = 1; // 確保封面圖可見
191            this.overlay.style.pointerEvents = 'auto'; // 允許交互
192        }
193
194        // 可選:隱藏或銷毀播放器 iframe
195        const iframe = document.getElementById(this.videoIframe);
196        if (iframe) {
197            iframe.style.display = 'none'; // 隱藏播放器
198        }
199    }
200    onPlayerReady = (e) => {
201
202        // console.log('onPlayerReady08');
203        if (this.youtubeSettings.useCoverImg) {
204            this.removerCoverImg();
205        }
206        if (this.youtubeSettings.autoPlay) {
207            e.target.mute().playVideo();
208        } else {
209            this.overlay.style.cursor = 'pointer';
210            this.overlay.addEventListener('click', () => {
211                e.target.playVideo();
212                this.checkLoopShowControls();
213            });
214        }
215    }
216
217    onPlayerStateChange = (event) => {
218        if (event.data === 0 && this.youtubeSettings.loop) {
219            if (this.firstRePlayVideo) {
220                this.player.loadVideoById(this.transformVideoId(this.videoId));
221                this.firstRePlayVideo = false;
222            } else {
223                event.target.playVideo();
224            }
225        }
226        if (event.data === 0 && !this.youtubeSettings.loop) {
227            this.checkLoopShowControls();
228        }
229    }
230    onPlayerError = (event) => {
231        console.log('YouTube Player Error:', event.data); // 可選:用於調試
232        // 錯誤碼參考:https://developers.google.com/youtube/iframe_api_reference#Events
233        // 2: 無效參數, 5: HTML5播放器錯誤, 100: 影片不存在, 101/150: 播放受限
234
235        // 當發生錯誤時,顯示封面圖
236        this.showFallbackCover();
237    }
238}
239

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.