aboutsummaryrefslogtreecommitdiffstats
path: root/youtube/static/js/watch.js
blob: acc6b28e28767d29642b03ea5c876efcb7e7794c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
const video = document.getElementById('js-video-player');

function changeQuality(selection) {
    var currentVideoTime = video.currentTime;
    var videoPaused = video.paused;
    var videoSpeed = video.playbackRate;
    var srcInfo;
    if (avMerge)
        avMerge.close();
    if (selection.type == 'uni'){
        srcInfo = data['uni_sources'][selection.index];
        video.src = srcInfo.url;
    } else {
        srcInfo = data['pair_sources'][selection.index];
        avMerge = new AVMerge(video, srcInfo, currentVideoTime);
    }
    video.currentTime = currentVideoTime;
    if (!videoPaused){
        video.play();
    }
    video.playbackRate = videoSpeed;
}

// Initialize av-merge
var avMerge;
if (data.using_pair_sources) {
    var srcPair = data['pair_sources'][data['pair_idx']];
    // Do it dynamically rather than as the default in jinja
    // in case javascript is disabled
    avMerge = new AVMerge(video, srcPair, 0);
}

// Quality selector
const qs = document.getElementById('quality-select');
if (qs) {
  qs.addEventListener('change', function(e) {
    changeQuality(JSON.parse(this.value))
  });
}

// Set up video start time from &t parameter
if (data.time_start != 0 && video) {video.currentTime = data.time_start};

// External video speed control
var speedInput = document.getElementById('speed-control');
speedInput.addEventListener('keyup', (event) => {
    if (event.key === 'Enter') {
        var speed = parseFloat(speedInput.value);
        if(!isNaN(speed)){
            video.playbackRate = speed;
        }
    }
});


// Playlist lazy image loading
if (data.playlist && data.playlist['id'] !== null) {
    // lazy load playlist images
    // copied almost verbatim from
    // https://css-tricks.com/tips-for-rolling-your-own-lazy-loading/
    // IntersectionObserver isn't supported in pre-quantum
    // firefox versions, but the alternative of making it
    // manually is a performance drain, so oh well
    var observer = new IntersectionObserver(lazyLoad, {

      // where in relation to the edge of the viewport, we are observing
      rootMargin: "100px",

      // how much of the element needs to have intersected
      // in order to fire our loading function
      threshold: 1.0

    });

    function lazyLoad(elements) {
      elements.forEach(item => {
        if (item.intersectionRatio > 0) {

          // set the src attribute to trigger a load
          item.target.src = item.target.dataset.src;

          // stop observing this element. Our work here is done!
          observer.unobserve(item.target);
        };
      });
    };

    // Tell our observer to observe all img elements with a "lazy" class
    var lazyImages = document.querySelectorAll('img.lazy');
    lazyImages.forEach(img => {
      observer.observe(img);
    });
}


// Autoplay
if (data.settings.related_videos_mode !== 0 || data.playlist !== null) {
    let playability_error = !!data.playability_error;
    let isPlaylist = false;
    if (data.playlist !== null && data.playlist['current_index'] !== null)
        isPlaylist = true;

    // read cookies on whether to autoplay
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie
    let cookieValue;
    let playlist_id;
    if (isPlaylist) {
        // from https://stackoverflow.com/a/6969486
        function escapeRegExp(string) {
            // $& means the whole matched string
            return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
        }
        playlist_id = data.playlist['id'];
        playlist_id = escapeRegExp(playlist_id);

        cookieValue = document.cookie.replace(new RegExp(
            '(?:(?:^|.*;\\s*)autoplay_'
            + playlist_id + '\\s*\\=\\s*([^;]*).*$)|^.*$'
        ), '$1');
    } else {
        cookieValue = document.cookie.replace(new RegExp(
            '(?:(?:^|.*;\\s*)autoplay\\s*\\=\\s*([^;]*).*$)|^.*$'
        ),'$1');
    }

    let autoplayEnabled = 0;
    if(cookieValue.length === 0){
        autoplayEnabled = 0;
    } else {
        autoplayEnabled = Number(cookieValue);
    }

    // check the checkbox if autoplay is on
    let checkbox = document.querySelector('.autoplay-toggle');
    if(autoplayEnabled){
        checkbox.checked = true;
    }

    // listen for checkbox to turn autoplay on and off
    let cookie = 'autoplay'
    if (isPlaylist)
        cookie += '_' + playlist_id;

    checkbox.addEventListener( 'change', function() {
        if(this.checked) {
            autoplayEnabled = 1;
            document.cookie = cookie + '=1; SameSite=Strict';
        } else {
            autoplayEnabled = 0;
            document.cookie = cookie + '=0; SameSite=Strict';
        }
    });

    if(!playability_error){
        // play the video if autoplay is on
        if(autoplayEnabled){
            video.play();
        }
    }

    // determine next video url
    let nextVideoUrl;
    if (isPlaylist) {
        let currentIndex = data.playlist['current_index'];
        if (data.playlist['current_index']+1 == data.playlist['items'].length)
            nextVideoUrl = null;
        else
            nextVideoUrl = data.playlist['items'][data.playlist['current_index']+1]['url'];

        // scroll playlist to proper position
        // item height + gap == 100
        let pl = document.querySelector('.playlist-videos');
        pl.scrollTop = 100*currentIndex;
    } else {
        if (data.related.length === 0)
            nextVideoUrl = null;
        else
            nextVideoUrl = data.related[0]['url'];
    }
    let nextVideoDelay = 1000;

    // go to next video when video ends
    // https://stackoverflow.com/a/2880950
    if (nextVideoUrl) {
        if(playability_error){
            videoEnded();
        } else {
            video.addEventListener('ended', videoEnded, false);
        }
        function nextVideo(){
            if(autoplayEnabled){
                window.location.href = nextVideoUrl;
            }
        }
        function videoEnded(e) {
            window.setTimeout(nextVideo, nextVideoDelay);
        }
    }
}