PageSourceSearch

https://makersweare.com/cast/cast.js

js makersweare.com collected 2026-10-03 18:29:53 UTC 16,450 bytes, 524 lines download raw bytes

1// Import cast framework
2if (window.chrome && !window.chrome.cast) {
3  var script = document.createElement("script");
4  script.src =
5    "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1";
6  document.head.appendChild(script);
7}
8
9// Castjs
10class Castjs {
11  // constructor takes optional options
12  constructor(opt = {}) {
13    // valid join policies
14    var joinpolicies = [
15      "tab_and_origin_scoped",
16      "origin_scoped",
17      "page_scoped",
18    ];
19
20    // only allow valid join policy
21    if (!opt.joinpolicies || joinpolicies.indexOf(opt.joinpolicy) === -1) {
22      opt.joinpolicy = "tab_and_origin_scoped";
23    }
24
25    // set default receiver ID if none provided
26    if (!opt.receiver || opt.receiver === "") {
27      opt.receiver = "CC1AD845";
28    }
29
30    // private variables
31    this._events = {};
32    this._player = null;
33    this._controller = null;
34
35    // public variables
36    this.version = "v4.1.2";
37    this.receiver = opt.receiver;
38    this.joinpolicy = opt.joinpolicy;
39    this.available = false;
40    this.connected = false;
41    this.device = "Chromecast";
42    this.src = "";
43    this.title = "";
44    this.description = "";
45    this.poster = "";
46    this.subtitles = [];
47    this.volumeLevel = 1;
48    this.muted = false;
49    this.paused = false;
50    this.time = 0;
51    this.timePretty = "00:00:00";
52    this.duration = 0;
53    this.durationPretty = "00:00:00";
54    this.progress = 0;
55    this.state = "disconnected";
56
57    // initialize chromecast framework
58    this._init();
59  }
60  _getBrowser() {
61    if (navigator.userAgent.toLowerCase().indexOf("firefox") > -1) {
62      return "Firefox: Casting is not supported in this browser.";
63    }
64    if (navigator.userAgent.toLowerCase().indexOf("opr/") > -1) {
65      return "Opera: Please enable casting, click here: https://bit.ly/2G1PMhD";
66    }
67    if (navigator.userAgent.toLowerCase().indexOf("iron safari") > -1) {
68      return "Iron Safari: Please enable casting, click here: https://bit.ly/2G1PMhD";
69    }
70  }
71  _init(tries = 0) {
72    // casting only works on chrome, opera, brave and vivaldi
73    if (
74      !window.chrome ||
75      !window.chrome.cast ||
76      !window.chrome.cast.isAvailable
77    ) {
78      if (tries++ > 20) {
79        return this.trigger(
80          "error",
81          "Casting is not supported in " + getBrowser(),
82        );
83      }
84      return setTimeout(this._init.bind(this), 250, tries);
85    }
86
87    // terminate loop
88    clearInterval(this.intervalIsAvailable);
89    // initialize cast API
90    cast.framework.CastContext.getInstance().setOptions({
91      receiverApplicationId: this.receiver,
92      autoJoinPolicy: this.joinpolicy,
93      language: "en-US",
94      resumeSavedSession: false,
95    });
96    // create remote player controller
97    this._player = new cast.framework.RemotePlayer();
98    this._controller = new cast.framework.RemotePlayerController(this._player);
99
100    // register callback events
101    this._controller.addEventListener(
102      "isConnectedChanged",
103      this._isConnectedChanged.bind(this),
104    );
105    this._controller.addEventListener(
106      "isMediaLoadedChanged",
107      this._isMediaLoadedChanged.bind(this),
108    );
109    this._controller.addEventListener(
110      "isMutedChanged",
111      this._isMutedChanged.bind(this),
112    );
113    this._controller.addEventListener(
114      "isPausedChanged",
115      this._isPausedChanged.bind(this),
116    );
117    this._controller.addEventListener(
118      "currentTimeChanged",
119      this._currentTimeChanged.bind(this),
120    );
121    this._controller.addEventListener(
122      "durationChanged",
123      this._durationChanged.bind(this),
124    );
125    this._controller.addEventListener(
126      "volumeLevelChanged",
127      this._volumeLevelChanged.bind(this),
128    );
129    this._controller.addEventListener(
130      "playerStateChanged",
131      this._playerStateChanged.bind(this),
132    );
133    this.available = true;
134    this.trigger("available");
135  }
136
137  _isMediaLoadedChanged() {
138    // don't update media info if not available
139    if (!this._player.isMediaLoaded) {
140      return;
141    }
142    // there is a bug where mediaInfo is not directly available
143    // so we are skipping one tick in the event loop, zzzzzzzzz
144    setTimeout(() => {
145      if (!this._player.mediaInfo) {
146        return;
147      }
148      // Update device name
149      this.device =
150        cast.framework.CastContext.getInstance()
151          .getCurrentSession()
152          .getCastDevice().friendlyName || this.device;
153
154      // Update media variables
155      this.src = this._player.mediaInfo.contentId;
156      this.title = this._player.title || null;
157      this.description = this._player.mediaInfo.metadata.subtitle || null;
158      this.poster = this._player.imageUrl || null;
159      this.subtitles = [];
160      this.volumeLevel = this.volumeLevel = Number(
161        this._player.volumeLevel.toFixed(1),
162      );
163      this.muted = this._player.isMuted;
164      this.paused = this._player.isPaused;
165      this.time = Math.round(this._player.currentTime, 1);
166      this.timePretty = this._controller.getFormattedTime(this.time);
167      this.duration = this._player.duration;
168      this.durationPretty = this._controller.getFormattedTime(
169        this._player.duration,
170      );
171      this.progress = this._controller.getSeekPosition(
172        this.time,
173        this._player.duration,
174      );
175      this.state = this._player.playerState.toLowerCase();
176
177      // Loop over the subtitle tracks
178      for (var i in this._player.mediaInfo.tracks) {
179        // Check for subtitle
180        if (this._player.mediaInfo.tracks[i].type === "TEXT") {
181          // Push to media subtitles array
182          this.subtitles.push({
183            label: this._player.mediaInfo.tracks[i].name,
184            src: this._player.mediaInfo.tracks[i].trackContentId,
185          });
186        }
187      }
188      // Get the active subtitle
189      var active = cast.framework.CastContext.getInstance()
190        .getCurrentSession()
191        .getSessionObj().media[0].activeTrackIds;
192      if (active.length && this.subtitles[active[0]]) {
193        this.subtitles[active[0]].active = true;
194      }
195    });
196  }
197  // Player controller events
198  _isConnectedChanged() {
199    this.connected = this._player.isConnected;
200    if (this.connected) {
201      this.device =
202        cast.framework.CastContext.getInstance()
203          .getCurrentSession()
204          .getCastDevice().friendlyName || this.device;
205    }
206    this.state = !this.connected ? "disconnected" : "connected";
207    this.trigger("statechange");
208    this.trigger(!this.connected ? "disconnect" : "connect");
209  }
210  _currentTimeChanged() {
211    var past = this.time;
212    this.time = Math.round(this._player.currentTime, 1);
213    this.duration = this._player.duration;
214    this.progress = this._controller.getSeekPosition(this.time, this.duration);
215    this.timePretty = this._controller.getFormattedTime(this.time);
216    this.durationPretty = this._controller.getFormattedTime(this.duration);
217    // Only trigger timeupdate if there is a difference
218    if (past != this.time && !this._player.isPaused) {
219      this.trigger("timeupdate");
220    }
221  }
222  _durationChanged() {
223    this.duration = this._player.duration;
224  }
225  _volumeLevelChanged() {
226    this.volumeLevel = Number(this._player.volumeLevel.toFixed(1));
227    if (this._player.isMediaLoaded) {
228      this.trigger("volumechange");
229    }
230  }
231  _isMutedChanged() {
232    var old = this.muted;
233    this.muted = this._player.isMuted;
234    if (old != this.muted) {
235      this.trigger(this.muted ? "mute" : "unmute");
236    }
237  }
238  _isPausedChanged() {
239    this.paused = this._player.isPaused;
240    if (this.paused) {
241      this.trigger("pause");
242    }
243  }
244  _playerStateChanged() {
245    this.connected = this._player.isConnected;
246    if (!this.connected) {
247      return;
248    }
249    this.device =
250      cast.framework.CastContext.getInstance()
251        .getCurrentSession()
252        .getCastDevice().friendlyName || this.device;
253    this.state = this._player.playerState.toLowerCase();
254    switch (this.state) {
255      case "idle":
256        this.state = "ended";
257        this.trigger("statechange");
258        this.trigger("end");
259        return this;
260      case "buffering":
261        this.time = Math.round(this._player.currentTime, 1);
262        this.duration = this._player.duration;
263        this.progress = this._controller.getSeekPosition(
264          this.time,
265          this.duration,
266        );
267        this.timePretty = this._controller.getFormattedTime(this.time);
268        this.durationPretty = this._controller.getFormattedTime(this.duration);
269        this.trigger("statechange");
270        this.trigger("buffering");
271        return this;
272      case "playing":
273        // we have to skip a tick to give mediaInfo some time to update
274        setTimeout(() => {
275          this.trigger("statechange");
276          this.trigger("playing");
277        });
278        return this;
279    }
280  }
281  // Class functions
282  on(event, cb) {
283    // If event is not registered, create array to store callbacks
284    if (!this._events[event]) {
285      this._events[event] = [];
286    }
287    // Push callback into event array
288    this._events[event].push(cb);
289    return this;
290  }
291  off(event) {
292    if (!event) {
293      // if no event name was given, reset all events
294      this._events = {};
295    } else if (this._events[event]) {
296      // remove all callbacks from event
297      this._events[event] = [];
298    }
299    return this;
300  }
301  trigger(event) {
302    // Slice arguments into array
303    var tail = Array.prototype.slice.call(arguments, 1);
304    // If event exist, call callback with callback data
305    for (var i in this._events[event]) {
306      this._events[event][i].apply(this, tail);
307    }
308    // dont call global event if error
309    if (event === "error") {
310      return this;
311    }
312    // call global event handler if exist
313    for (var i in this._events["event"]) {
314      this._events["event"][i].apply(this, [event]);
315    }
316    return this;
317  }
318  cast(src, metadata = {}, customData = {}) {
319    // We need a source! Don't forget to enable CORS
320    if (!src) {
321      return this.trigger("error", "No media source specified.");
322    }
323    metadata.src = src;
324    // Update media variables with user input
325    for (var key in metadata) {
326      if (metadata.hasOwnProperty(key)) {
327        this[key] = metadata[key];
328      }
329    }
330    // Time to request a session!
331    cast.framework.CastContext.getInstance()
332      .requestSession()
333      .then(
334        () => {
335          if (!cast.framework.CastContext.getInstance().getCurrentSession()) {
336            return this.trigger(
337              "error",
338              "Could not connect with the cast device",
339            );
340          }
341          // Create media cast object
342          var mediaInfo = new chrome.cast.media.MediaInfo(this.src);
343          mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata();
344
345          // This part is the reason why people love this library <3
346          if (this.subtitles.length) {
347            // I'm using the Netflix subtitle styling
348            // chrome.cast.media.TextTrackFontGenericFamily.CASUAL
349            // chrome.cast.media.TextTrackEdgeType.DROP_SHADOW
350            mediaInfo.textTrackStyle = new chrome.cast.media.TextTrackStyle();
351            mediaInfo.textTrackStyle.backgroundColor = "#00000000";
352            mediaInfo.textTrackStyle.edgeColor = "#00000016";
353            mediaInfo.textTrackStyle.edgeType = "DROP_SHADOW";
354            mediaInfo.textTrackStyle.fontFamily = "CASUAL";
355            mediaInfo.textTrackStyle.fontScale = 1.0;
356            mediaInfo.textTrackStyle.foregroundColor = "#FFFFFF";
357
358            var tracks = [];
359            for (var i in this.subtitles) {
360              // chrome.cast.media.TrackType.TEXT
361              // chrome.cast.media.TextTrackType.CAPTIONS
362              var track = new chrome.cast.media.Track(i, "TEXT");
363              track.name = this.subtitles[i].label;
364              track.subtype = "CAPTIONS";
365              track.trackContentId = this.subtitles[i].src;
366              track.trackContentType = "text/vtt";
367              // This bug made me question life for a while
368              track.trackId = parseInt(i);
369              tracks.push(track);
370            }
371            mediaInfo.tracks = tracks;
372          }
373          // Let's prepare the metadata
374          mediaInfo.metadata.images = [new chrome.cast.Image(this.poster)];
375          mediaInfo.metadata.title = this.title;
376          mediaInfo.metadata.subtitle = this.description;
377          mediaInfo.customData = customData;
378          // Prepare the actual request
379          var request = new chrome.cast.media.LoadRequest(mediaInfo);
380          // Didn't really test this currenttime thingy, dont forget
381          request.currentTime = this.time;
382          request.autoplay = !this.paused;
383          // If multiple subtitles, use the active: true one
384          if (this.subtitles.length) {
385            for (var i in this.subtitles) {
386              if (this.subtitles[i].active) {
387                request.activeTrackIds = [parseInt(i)];
388                break;
389              }
390            }
391          }
392          // Here we go!
393          cast.framework.CastContext.getInstance()
394            .getCurrentSession()
395            .loadMedia(request)
396            .then(
397              () => {
398                // Update device name
399                this.device =
400                  cast.framework.CastContext.getInstance()
401                    .getCurrentSession()
402                    .getCastDevice().friendlyName || this.device;
403                return this;
404              },
405              (err) => {
406                return this.trigger("error", err);
407              },
408            );
409        },
410        (err) => {
411          if (err !== "cancel") {
412            this.trigger("error", err);
413          }
414          return this;
415        },
416      );
417  }
418  seek(seconds, isPercentage) {
419    // if seek(15, true) we assume 15 is percentage instead of seconds
420    if (isPercentage) {
421      seconds = this._controller.getSeekTime(seconds, this._player.duration);
422    }
423    this._player.currentTime = seconds;
424    this._controller.seek();
425    return this;
426  }
427  volume(float) {
428    this._player.volumeLevel = float;
429    this._controller.setVolumeLevel();
430    return this;
431  }
432  play() {
433    if (this.paused) {
434      this._controller.playOrPause();
435    }
436    return this;
437  }
438  pause() {
439    if (!this.paused) {
440      this._controller.playOrPause();
441    }
442    return this;
443  }
444  mute() {
445    if (!this.muted) {
446      this._controller.muteOrUnmute();
447    }
448    return this;
449  }
450  unmute() {
451    if (this.muted) {
452      this._controller.muteOrUnmute();
453    }
454    return this;
455  }
456  // subtitle allows you to change active subtitles while casting
457  subtitle(index) {
458    // this is my favorite part of castjs
459    // prepare request to edit the tracks on current session
460    var request = new chrome.cast.media.EditTracksInfoRequest([
461      parseInt(index),
462    ]);
463    cast.framework.CastContext.getInstance()
464      .getCurrentSession()
465      .getSessionObj()
466      .media[0].editTracksInfo(
467        request,
468        () => {
469          // after updating the device we should update locally
470          // loop trough subtitles
471          for (var i in this.subtitles) {
472            // remove active key from all subtitles
473            delete this.subtitles[i].active;
474            // if subtitle matches given index, we set to true
475            if (i == index) {
476              this.subtitles[i].active = true;
477            }
478          }
479          return this.trigger("subtitlechange");
480        },
481        (err) => {
482          // catch any error
483          return this.trigger("error", err);
484        },
485      );
486  }
487  // disconnect will end the current session
488  disconnect() {
489    cast.framework.CastContext.getInstance().endCurrentSession(true);
490    this._controller.stop();
491
492    // application variables
493    this.connected = false;
494    this.device = "Chromecast";
495
496    // media variables
497    this.src = "";
498    this.title = "";
499    this.description = "";
500    this.poster = "";
501    this.subtitles = [];
502
503    // player variable
504    this.volumeLevel = 1;
505    this.muted = false;
506    this.paused = false;
507    this.time = 0;
508    this.timePretty = "00:00:00";
509    this.duration = 0;
510    this.durationPretty = "00:00:00";
511    this.progress = 0;
512    this.state = "disconnected";
513
514    this.trigger("disconnect");
515    return this;
516  }
517}
518
519// Expose Castjs globally for browser usage
520window.Castjs = Castjs;
521
522if (typeof module !== "undefined") {
523  module.exports = Castjs;
524}

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.