PageSourceSearch

https://benshi.fr/build/js/otto-player.js

js benshi.fr collected 2026-09-24 11:44:13 UTC 148,714 bytes, 5,021 lines download raw bytes

1
2var OPBandwith = (function () {
3
4  var _last = 5;
5  var _history = [];
6
7  function calculate(size, downloadTime) {
8    return Math.round(((size * 8) / (downloadTime)) / 1000);
9  }
10
11  function save(bandwith) {
12    _history.unshift(bandwith);
13    _history = _history.slice(0, 50);
14  }
15
16  function getMinFromLast(number) {
17    var sliceArr = _history.slice(0, number);
18    return Math.min.apply(Math, sliceArr);
19  }
20
21  function getHistory() {
22    return _history;
23  }
24
25  return {
26    'calculate' : calculate,
27    'save' : save,
28    'getMinFromLast' : getMinFromLast,
29    'getHistory' : getHistory,
30  }
31})();
32
33
34var OPChunk = (function () {
35
36  var _last = 0;
37  var _biggest = 0;
38  var _history = [];
39  var _duration = 0;
40
41  /**
42   * Get chunk duration
43   * @return {[type]} [description]
44   */
45  function getDuration() {
46    return _duration;
47  }
48
49  function getSize() {
50    return _biggest;
51  }
52
53  function getMaxSize(number) {
54    var maxSize;
55    for (var item = 0; item <= _history.length; item++) {
56      if (maxSize < _history[i].size) {
57        maxSize = _history[i];
58      }
59    }
60
61    return maxSize;
62  }
63
64  function getNextTime() {
65    return _nextTime = OPChunk.getDownloadTime(OPBandwith.getMinFromLast(3), estimatedSize) + data.timeToFirstByte;
66  }
67
68  /**
69   * get chunk  Download Time in seconds
70   * @param  {[type]} bitrate [description]
71   * @return {[type]}         [description]
72   */
73  function getDownloadTime(bitrate, size) {
74    if (typeof size === 'undefined' ) {
75      return (_biggest / 1000) / (bitrate / 8);
76    }
77    return (size / 1000) / (bitrate / 8);
78  }
79
80  function save(size, bitrate) {
81    _history.unshift({bitrate : bitrate, size: size})
82    _history = _history.slice(0, 50);
83    if (size > _biggest) {
84      _biggest = size;
85    }
86  }
87
88  /**
89   * Set chunk duration
90   * @param {[type]} duration [description]
91   */
92  function setDuration(duration) {
93    _duration = Number.parseInt(duration);
94    return true;
95  }
96
97  return {
98    'getSize' : getSize,
99    'getMaxSize' : getMaxSize,
100    'getDownloadTime' : getDownloadTime,
101    'getDuration': getDuration,
102    'save' : save,
103    'setDuration': setDuration,
104  }
105})();
106
107/**
108 * OTTO Player Config
109 *
110 * @author Jody JUSTINE <[email protected]>
111 */
112var OPConfig = (function () {
113  const _key = $('meta[name="bitmovin:player_licence_id"]').attr("content") ?? '';
114  const _analyticsKey = $('meta[name="bitmovin:analytics_licence_id"]').attr("content") ?? '';
115  const _chromeCastAppId = $('meta[name="chromecast:app_id"]').attr("content") ?? '';
116  const _playerUrl = $('meta[name="asset:url"]').attr("content") ?? '';
117  const _timeNow = Date.now();  // ES6 const
118  const _levelBuffer = 48;
119  const _volumeLevel = 50;
120
121  var _maxDesktopBitrate = '7000kbps';
122  var _maxDesktopHeight = 1080;
123  var _maxDesktopWidth = 1920;
124  var _maxMobileBitrate = '5000kbps';
125  var _maxMobileHeight = 1080;
126  var _maxMobileWidth = 1920;
127
128  /**
129   * Set player video limitations
130   *
131   * @param Object limitations [object containing video limitations
132   * @author Thomas TÉCHER <[email protected]>
133   */
134
135  function setLimitations(limitations) {
136    if (typeof limitations.desktop !== 'undefined') {
137      if (typeof limitations.desktop.max_bitrate !== 'undefined') {
138        _maxDesktopBitrate = limitations.desktop.max_bitrate;
139      }
140      if (typeof limitations.desktop.max_height !== 'undefined') {
141        _maxDesktopHeight = limitations.desktop.max_height;
142      }
143      if (typeof limitations.desktop.max_width !== 'undefined') {
144        _maxDesktopWidth = limitations.desktop.max_width;
145      }
146    }
147    if (typeof limitations.mobile !== 'undefined') {
148      if (typeof limitations.mobile.max_bitrate !== 'undefined') {
149        _maxMobileBitrate = limitations.mobile.max_bitrate;
150      }
151      if (typeof limitations.mobile.max_height !== 'undefined') {
152        _maxMobileHeight = limitations.mobile.max_height;
153      }
154      if (typeof limitations.mobile.max_width !== 'undefined') {
155        _maxMobileWidth = limitations.mobile.max_width;
156      }
157    }
158  }
159
160  /**
161   * [get description]
162   *
163   * @return {[type]} [description]
164   * @author Jody JUSTINE <[email protected]>
165   */
166  function get() {
167    return {
168      key: _key,
169
170      ui: false, // disable the built-in UI
171
172      adaptation: {
173        desktop: {
174          preload: true,
175          exclude: true,
176          limitToPlayerSize: true,
177          maxStartupBitrate: '1000kbps',
178          onVideoAdaptation: _videoAdaptationHandler,
179          bitrates: {
180            // All qualities above this threshold will not be selected by the ABR logic
181            // These qualities are still available for manual quality selection unless the exclude flag is set to true
182            maxSelectableVideoBitrate: _maxDesktopBitrate
183          },
184          resolution: {
185            maxSelectableVideoHeight: _maxDesktopHeight,
186            maxSelectableVideoWidth: _maxDesktopWidth
187          }
188        },
189        mobile: {
190          preload: true,
191          exclude: true,
192          limitToPlayerSize: true,
193          maxStartupBitrate: '1000kbps',
194          onVideoAdaptation: _videoAdaptationHandler,
195          bitrates: {
196            // All qualities above this threshold will not be selected by the ABR logic
197            // These qualities are still available for manual quality selection unless the exclude flag is set to true
198            maxSelectableVideoBitrate: _maxMobileBitrate
199          },
200          resolution: {
201            maxSelectableVideoHeight: _maxMobileHeight,
202            maxSelectableVideoWidth: _maxMobileWidth
203          }
204        },
205      },
206
207      advertising: {},
208
209      remotecontrol: {
210        type: 'googlecast',
211        receiverApplicationId: _chromeCastAppId
212      },
213
214      playback: {
215        autoplay: true,
216        muted:    false,
217        volume:   _volumeLevel,
218        preferredTech : _getPreferredTech(),
219        audioLanguage : OPHelpers.getAudioPreferences(),
220        subtitleLanguage : OPHelpers.getSubtitlesPreferences()
221      },
222
223      location: {
224        "ui_css": _getUiCssUrl(),
225        "ui":     _getUiUrl()
226      },
227
228      analytics: {
229          key: _analyticsKey
230      },
231
232      logs: {
233        bitmovin: true,
234        level:    'error',
235      },
236
237      tweaks : {
238        max_buffer_level : _levelBuffer,
239      }
240    }
241  }
242
243  function _videoAdaptationHandler (e) {
244    return ottoPlayer.videoAdaptationHandler(e);
245  }
246
247  function _getUiCssUrl() {
248    return _playerUrl + "/css/otto-player-ui.min.css?t=" + _timeNow;
249  }
250
251  function _getUiUrl() {
252    return _playerUrl + "/js/bitmovinplayer-ui.min.js?t=" + _timeNow;
253  }
254
255  function _getPreferredTech (){
256    var currentOs = OPHelpers.getOsName();
257    var currentBrowser = OPHelpers.getBrowserName();
258
259    var defaultTech = [
260        { player: 'native', streaming: 'progressive'},
261        { player: 'html5', streaming: 'smooth', exclude: true},
262        { player: 'flash', streaming: 'dash', exclude: true},
263        { player: 'flash', streaming: 'hls', exclude: true},
264        { player: 'flash', streaming: 'smooth', exclude: true},
265        { player: 'native-flash', streaming: 'hls', exclude: true},
266      ];
267
268    var specificTech = [];
269
270    if (currentOs == OP.OS.ANDROID && (currentBrowser == OP.BROWSER.OPERA || currentBrowser == OP.BROWSER.FIREFOX)) {
271      specificTech = [];
272    } else if (currentOs == OP.OS.ANDROID) {
273      specificTech = [
274          { player: 'html5', streaming: 'dash'},
275        ];
276
277    } else if (currentOs == OP.OS.IOS || currentBrowser == OP.BROWSER.SAFARI) {
278      specificTech = [
279          { player: 'native', streaming: 'hls'},
280          { player: 'html5', streaming: 'hls'},
281        ];
282    } else {
283      specificTech = [
284        { player: 'html5', streaming: 'dash'},
285        { player: 'html5', streaming: 'hls'},
286      ];
287    }
288
289    console.debug('specificTech', specificTech);
290
291    return specificTech.concat(defaultTech);
292  }
293
294  function getWantedTech () {
295    var currentOs = OPHelpers.getOsName();
296    var currentBrowser = OPHelpers.getBrowserName();
297
298    if (currentOs == OP.OS.ANDROID && currentBrowser == OP.BROWSER.OPERA) {
299      retval = ['native.progressive'];
300    } else {
301      retval = ['html5.dash', 'html5.hls', 'native.progressive'];
302    }
303
304    return retval;
305  }
306
307  return {
308      'getConfig': get,
309      'getWantedTech': getWantedTech,
310      'setLimitations': setLimitations,
311  }
312})();
313
314
315const MODAL_ERROR     = '#modal-player-alert';
316const MODAL_PLAYER    = '#movie-video-modal';
317const MODAL_LIMIT     = '#modal-stream-limitation';
318const MODAL_LIMIT_VIEW_COUNT = '#modal-stream-limitation-view-count';
319const modalBadBrowser = '#modal_wrong_browser';
320const overlayLoader   = '.overlay-loader';
321const MODAL_INFOS       = MODAL_PLAYER + ' .mvi';
322const MODAL_INFOS_BLOCK = MODAL_INFOS + ' .mvi-';
323const MODAL_INFOS_TEXT  = ' .mvi-text';
324
325const MAX_RETRY = 6;
326const COUNT_DOWN_NUMBER = 5;
327const INIT_PREVIOUS = -1;
328
329class OP {
330
331  static get OS() {
332    return {
333      ANDROID: 'Android',
334      IOS: 'iOS',
335      MAC_OS: 'Mac OS'
336    };
337  }
338
339  static get BROWSER() {
340    return {
341      CHROME: 'Chrome',
342      OPERA: 'Opera',
343      FIREFOX: 'Firefox',
344      SAFARI: 'Safari'
345    };
346  }
347
348  static get debug() {
349    return oDebug;
350  }
351
352};
353
354/**
355 * OTTO Player Source
356 *
357 * @author Jody JUSTINE <[email protected]>
358 */
359var OPSource = (function () {
360
361  var _currentPicture;
362  var _currentTitle = '';
363  var _analyticsTitle = '';
364  var _analyticsProgramId = 0;
365  var _currentSubTitle = '';
366
367  function getPicture(picture) {
368    return _currentPicture
369  }
370
371  function setPicture(picture) {
372    _currentPicture = picture
373    return true
374  }
375
376  function getTitle(title) {
377    return _currentTitle
378  }
379
380  function setTitle(title) {
381    _currentTitle = title
382    return true
383  }
384
385  function getAnalyticsTitle(title) {
386    return _analyticsTitle
387  }
388
389  function setAnalyticsTitle(title) {
390    _analyticsTitle = title
391    return true
392  }
393
394  function getAnalyticsProgramId() {
395    return _analyticsProgramId;
396  }
397
398  function setAnalyticsProgramId(id) {
399    _analyticsProgramId = id;
400  }
401
402  /**
403   * set current used subtitle
404   *
405   * @param {[type]} lang    [description]
406   * @param {[type]} enabled [description]
407   * @author Jody JUSTINE <[email protected]>
408   * @since  25/10/2019
409   */
410  function setCurrentSubtitle (lang, enabled) {
411    var obj = {};
412
413    obj.lang = lang;
414
415    if (typeof enabled == 'boolean') {
416      obj.enabled = enabled;
417    } else if (enabled == 'subtitledisabled') {
418      obj.enabled = false;
419    } else if (enabled == 'subtitleenabled') {
420      obj.enabled = true;
421    } else {
422      obj.enabled = false;
423    }
424
425    _currentSubTitle =  obj;
426    return true;
427  }
428
429  /**
430   * [getAllSourceData description]
431   * @param  {[type]} data [description]
432   * @return {[type]}      [description]
433   */
434  function getAllSourceData(data) {
435    var defaultSource  = _getDefaultSource(data);
436    var mainData       = _getMain(data);
437    var videoData      = _getPlayerVideo(data);
438    var drmData        = _getDrm(data);
439    var analyticsData  = _getAnalytics(data);
440    var thumbnailsData = _getThumbnails(data);
441    var subtitlesData  = _getSubtitles(data);
442    var posterData     = _getPoster(data);
443    var mainLanguage   = _getMainLanguage(data);
444    var audiotracksDetails = _getAudiotracksDetails(data);
445    var others         = _getOthers(data);
446
447    var merged = Object.assign(
448      defaultSource, mainData, videoData, drmData, analyticsData, thumbnailsData,
449      subtitlesData, posterData, mainLanguage, audiotracksDetails, others
450    );
451
452    return merged;
453  }
454
455  /**
456   * [getPlayerVideo description]
457   *
458   * @param  {[type]} data [description]
459   * @return {oject}      [description]
460   *
461   * @private
462   */
463  function _getPlayerVideo(data) {
464    var $obj = {};
465
466    if (data.mp4) {
467      $obj.progressive = data.mp4;
468    }
469
470    if (data.dash) {
471      $obj.dash = data.dash;
472    }
473
474    if (data.hls) {
475      $obj.hls = data.hls;
476    }
477
478    if (data.smooth) {
479      $obj.smooth = data.smooth;
480    }
481
482    return $obj;
483  }
484
485  /**
486   * [getPlayerDefaultSource description]
487   * @return {[type]} [description]
488   *
489   * @private
490   */
491  function _getDefaultSource(data) {
492    var defaultSource = {
493        labeling: {
494            dash: {
495                tracks: _getLangTagName,
496                subtitles: _getLangTagName,
497                qualities: _getQualityLabels
498            },
499            hls: {
500                tracks: _getLangTagName,
501                subtitles: _getLangTagName,
502                qualities: _getQualityLabels
503            },
504            smooth: {
505                tracks: _getLangTagName,
506                subtitles: _getLangTagName
507            }
508        }
509    };
510
511    return defaultSource;
512  }
513
514  /**
515   * [getMain description]
516   * @param  {[type]} data [description]
517   * @return {[type]}      [description]
518   *
519   * @private
520   */
521  function _getMain(data) {
522    var $obj = {};
523    try {
524      if (_currentTitle) {
525        $obj.title = _currentTitle;
526      } else if (data.title) {
527        $obj.title = data.title;
528      }
529
530      if (data.description) {
531        $obj.description = data.description;
532      }
533
534      if (data.poster) {
535        $obj.poster = data.poster;
536      } else if (_currentPicture != '') {
537        $obj.poster = _currentPicture;
538      }
539    } catch(error) {
540      console.error('adding main source failed');
541      console.error(error);
542      $obj = {};
543    }
544
545    return $obj;
546  }
547
548  /**
549   * [getDrm description]
550   * @param  {[type]} data [description]
551   * @return {[type]}      [description]
552   *
553   * @private
554   */
555  function _getDrm(data) {
556    var $obj = {};
557
558    if ('widevine' in data) {
559      $obj.widevine = {
560        'LA_URL' : data.widevine
561      };
562    }
563
564    if ('playready' in data) {
565      $obj.playready = {'LA_URL' : data.playready};
566    }
567
568    if ('fairplay' in data && 'certificate' in data.fairplay && 'url' in data.fairplay) {
569      $obj.fairplay = {
570        certificateURL: data.fairplay.certificate,
571        LA_URL: data.fairplay.url,
572
573        prepareMessage: function(event, session) {
574              return event.message;
575            },
576            prepareContentId: function(contentId) {
577              var link = document.createElement('a');
578              link.href = contentId;
579              return link.hostname;
580            },
581            prepareLicense: function(license) {
582              return new Uint8Array(license);
583            },
584            headers: {
585              "Content-Type": 'application/octet-stream'
586            },
587            useUint16InitData: true,
588            licenseResponseType: 'arraybuffer'
589      }
590    }
591
592    return { 'drm' : $obj};
593  }
594
595
596  /**
597   * [getThumbnails description]
598   * @param  {[type]} data [description]
599   * @return {[type]}      [description]
600   *
601   * @private
602   */
603  function _getThumbnails(data) {
604    var $obj = {};
605
606    try {
607      if ('thumbnail_url' in data && data.thumbnail_url != '') {
608        $obj.url = data.thumbnail_url;
609      }
610    } catch(error) {
611      console.error('get Thumbnails failed');
612    }
613
614    return { 'thumbnailTrack' : $obj};
615  }
616
617
618  /**
619   * Get player subtitltes format for bitmovin
620   *
621   * @param  {object} data [description]
622   * @return {object}      [description]
623   *
624   * @private
625   * @author Jody JUSTINE <[email protected]>
626   * @since  25/10/2019
627   */
628  function _getSubtitles(data) {
629    var $obj = [];
630    var currentOs = OPHelpers.getOsName();
631    var currentBrowser = OPHelpers.getBrowserName();
632
633    if (!('subtitles' in data)) {
634      return $obj;
635    }
636
637    // if (currentOs == OP.OS.IOS || currentBrowser == OP.BROWSER.SAFARI) {
638    //   console.log('No need to add external subtitles...');
639    //   return $obj;
640    // }
641
642    data.subtitles.forEach(function(subtitle) {
643      try {
644          var subtitleData = {
645            id:      subtitle.id,
646            lang:    subtitle.lang,
647            label:   subtitle.label,
648            url:     subtitle.url,
649            kind:    subtitle.kind,
650            enabled: typeof subtitle.enabled != 'undefined' ? subtitle.enabled : false,
651          }
652
653          if (_currentSubTitle.lang == subtitle.lang) {
654            subtitleData.enabled = _currentSubTitle.enabled
655          }
656
657          $obj.push(subtitleData)
658      } catch(error) {
659        console.error('adding subtitle '+ subtitle.id +' failed');
660        console.debug(subtitleData);
661        console.error(error);
662      }
663    })
664
665    return { 'subtitleTracks' : $obj};
666  }
667
668  /**
669   * [_getAnalytics description]
670   * @param  {[type]} data [description]
671   * @return {[type]}      [description]
672   *
673   * customData1 : Platform ID
674   * customData2 : Platform Name
675   * customData3 : User ID
676   * customData4 : User Email
677   * customData5 : Program ID (not episode_id, not season_id, not season_program_id, ... the program ID of the content directly linked to this video)
678   * 
679   * @private
680   */
681  function _getAnalytics(data) {
682    var $obj = {};
683    try {
684      $obj.videoId  = data.id;
685      $obj.title    = _analyticsTitle ? _analyticsTitle : data.title;
686
687      $obj.customData1 = data.platform_id;
688      $obj.customData2 = data.platform_name;
689  
690      if (data.user_id ?? null) {
691        $obj.customData3 = data.user_id;
692        $obj.user_id = data.user_id;
693      }
694  
695      if (data.user_email ?? null) {
696        $obj.customData4 = data.user_email;
697      }
698
699      if(typeof(_analyticsProgramId) !== 'undefined' && _analyticsProgramId !== 0){
700        $obj.customData5 = _analyticsProgramId;
701      }
702    } catch(error) {
703      console.log(error);
704      console.error('get analytics failed');
705    }
706
707    return { 'analytics' : $obj }
708  }
709
710  /**
711   * [_getPoster description]
712   * @param  {[type]} data [description]
713   * @return {[type]}      [description]
714   *
715   * @private
716   */
717  function _getPoster(data) {
718    try {
719      return { 'poster' : _currentPicture }
720    } catch(error) {
721      console.error('get poster failed');
722      console.error(error);
723      return {}
724    }
725  }
726
727  function _getMainLanguage(data){
728    return {'mainLanguage' : data.main_language};
729  }
730
731  function _getAudiotracksDetails(data){
732    return {'audiotracksDetails' : data.audiotracks_details};
733  }
734
735  function _getOthers(data){
736    return {'pauseOnHide' : data.pause_on_hide};
737  }
738
739  /**
740   * Return label
741   *
742   * @param  {[type]} data [description]
743   * @return {string}      [description]
744   *
745   * @private
746   *
747   * @author Jody JUSTINE <[email protected]>
748   */
749  var _getQualityLabels = function (data) {
750    try {
751      if (typeof OPHelpers !== 'undefined') {
752        return OPHelpers.getQualityLabels(data);
753      } else {
754        return '';
755      }
756    } catch (e) {
757      console.error(e)
758      return '';
759    }
760  };
761
762 /**
763   * Return label
764   *
765   * @param  {string} iso [description]
766   * @return {string}      [description]
767   *
768   * @private
769   *
770   * @author Thomas Técher <[email protected]>
771   */
772  var _getLangTagName = function (track) {
773    try {
774      if (typeof OPHelpers !== 'undefined') {
775        return OPHelpers.getLangTagName(track);
776      } else {
777        return '';
778      }
779    } catch (e) {
780      console.error(e)
781      return '';
782    }
783  };
784
785  var onResize = function () {
786
787  }
788
789  return {
790      'getSourceData': getAllSourceData,
791      'setPicture' :   setPicture ,
792      'setTitle' :     setTitle,
793      'setAnalyticsTitle' : setAnalyticsTitle,
794      'setAnalyticsProgramId' : setAnalyticsProgramId,
795      'setCurrentSubtitle' : setCurrentSubtitle
796  }
797})();
798
799
800  function openDebugTab(evt, cityName) {
801    var i, x, tablinks;
802    x = document.getElementsByClassName('mvi-container');
803    //Hide all container tabs
804    for (i = 0; i < x.length; i++) {
805      x[i].classList.remove('show');
806    }
807    //Active to inactive
808    tablinks = document.getElementsByClassName('mvi-tablink');
809    for (i = 0; i < x.length; i++) {
810      tablinks[i].classList.remove('active');
811    }
812    //display wanted container
813    document.getElementById(cityName).classList.add('show');
814    //Active current tabs
815    evt.currentTarget.classList.add('active');
816  }
817
818var OPDebug = (function () {
819
820  var _isDebugActive = false;
821
822  function setDebug()
823  {
824    try {
825      var retval = OPHelpers.getUrlParameter('debug');
826      _isDebugActive = Boolean(retval);
827
828      if (_isDebugActive) {
829        var modal = MODAL_PLAYER.replace('#', '');
830        document.getElementById(modal).firstElementChild.classList.remove('modal-dialog-centered');
831      }
832
833    } catch (error) {
834      console.debug(error);
835    }
836  }
837
838  function setModalDebugInfos(id, value) {
839    try {
840      if (!_isDebugActive) {
841        return;
842      }
843
844      id = id.replace(/[^-a-z0-9]/ig,'');
845
846      $(MODAL_INFOS_BLOCK + id + MODAL_INFOS_TEXT).html(value)
847    } catch (error) {
848      console.debug(error)
849    }
850  }
851
852  function log(item, desc, type) {
853    try {
854      if (!_isDebugActive) {
855        return;
856      }
857
858      var textList = [];
859      separator       = ' : ';
860      var description = typeof desc !== 'undefined' ? desc + separator: '';
861      var type = typeof type !== 'undefined' ? type : '';
862
863      $( ".debug" ).show();
864      displayPlayerDebug();
865
866      if (typeof item ==  'array' || typeof item ==  'object') {
867        textList.push(description + JSON.stringify(item));
868        console.debug(item)
869      } else {
870        textList.push(description + item);
871        console.debug(description + item);
872      }
873
874      for (text in textList) {
875        var newLine = $('<li class="debug-' + type + '"></li>').text(textList[text]);   // Create with jQuery
876        $(MODAL_INFOS_BLOCK + 'log' + MODAL_INFOS_TEXT).prepend(newLine);
877
878      }
879    } catch(error) {
880      console.debug(error)
881      console.debug(item)
882    }
883  }
884
885
886  function error(item, desc) {
887    log(item, desc, 'error');
888  }
889
890  function displayPlayerDebug() {
891    try {
892      $(MODAL_INFOS).addClass('show');
893    } catch (error) {
894      console.debug(error);
895    }
896  }
897
898  function sendReporting()
899  {
900    var formattedBody = $('.debug').text();
901    var mailToLink = "mailto:[email protected]?body=" + encodeURIComponent(formattedBody);
902    window.location.href = mailToLink;
903  }
904
905
906  function setBrowser() {
907    if (!_isDebugActive) {
908      return;
909    }
910
911    try {
912      setModalDebugInfos('browser', OPHelpers.getOsName() + '.' + OPHelpers.getOsVersion() +  ' / ' + OPHelpers.getBrowserName() + '.' + OPHelpers.getBrowserMajor() +' / ' + OPHelpers.getDeviceModel());
913    } catch (e) {
914      setModalDebugInfos('browser', 'n/a');
915    }
916  }
917
918  function setLatency() {
919    if (!_isDebugActive) {
920      return;
921    }
922
923    try {
924      setModalDebugInfos('latency', OPLatency.getMaxFromLast(5) + 's');
925    } catch (e) {
926      setModalDebugInfos('latency', 'n/a');
927    }
928  }
929
930  function setFrames(player) {
931    if (!_isDebugActive) {
932      return;
933    }
934
935    setModalDebugInfos('dropped: ', player.getDroppedVideoFrames()) //integer
936  }
937
938  function setBuffer(player) {
939    if (!_isDebugActive) {
940      return;
941    }
942
943    try {
944      setModalDebugInfos('buffer', player.buffer.getLevel(bitmovin.player.BufferType.ForwardDuration, bitmovin.player.MediaType.Video).level.toFixed(2));
945    } catch (e) {
946      setModalDebugInfos('buffer', 'n/a');
947    }
948  }
949
950  function setStalledTime(player) {
951    if (!_isDebugActive) {
952      return;
953    }
954
955    try {
956      setModalDebugInfos('stalled: ', player.getTotalStalledTime());
957    } catch (e) {
958      setModalDebugInfos('stalled: ', 'n/a');
959    }
960  }
961
962  function setTech(player) {
963    if (!_isDebugActive) {
964      return;
965    }
966
967    try {
968      setModalDebugInfos('tech', JSON.stringify(player.getSupportedTech()));
969    } catch (e) {
970      setModalDebugInfos('tech: ', 'n/a');
971    }
972  }
973
974  function setDrm(player) {
975    try {
976      player.getSupportedDRM().then(function(data) {
977        OPDebug.setModalDebugInfos('drm2', JSON.stringify(data));
978      }).catch(function (error) {
979        console.debug('getSupportedDRM error')
980        console.debug(error)
981      })
982    } catch (e) {
983      setModalDebugInfos('drm2: ', 'n/a');
984    }
985  }
986
987  return {
988    'setModalDebugInfos': setModalDebugInfos,
989    'setDebug': setDebug,
990    'log':   log,
991    'error': error,
992    'sendReporting': sendReporting,
993    'setBrowser': setBrowser,
994    'setLatency': setLatency,
995    'setFrames':  setFrames,
996    'setBuffer':  setBuffer,
997    'setStalledTime': setStalledTime,
998    'setTech': setTech,
999    'setDrm':  setDrm,
1000  }
1001})();
1002
1003
1004/**
1005 * Use var with conventions like ALL_CAPS to show that certain values should not be modified
1006 */
1007  var ottoPlayer = (function () {
1008
1009    var player;
1010    const playerHtmlId = $('#player-drm');
1011    const playerElement = $(playerHtmlId);
1012    var uiManager;
1013    var previous = INIT_PREVIOUS;
1014
1015    var videoTypeFeature = 'feature';
1016    var videoTypeTrailer = 'trailer';
1017    var videoTypeMain = 'main';
1018    var videoTypeBonus = 'bonus';
1019
1020    var currentEpisode;
1021    var currentModal;
1022
1023    var savedPlayerData = {}
1024
1025    var wantedTech      = ''
1026    var techTry         = []
1027    var currentTech     = []
1028
1029    var availableQualities;
1030    var minLimit = 0;
1031    var maxLimit = Infinity;
1032
1033    var availableRepresentations = [];
1034    var bufferVideoItem = null
1035
1036    var lastSuggested = null;
1037    // Currently treating error, do not run process error.
1038    var isTreatingError = false;
1039    // Estimated time to download next video chunk
1040    var _nextTime = 0;
1041    var _startTime = 0;
1042    var _errorHistory = [];
1043    var _endOfStream = false;
1044    var _seekTime = 0;
1045
1046    var _getDataAndSetPlayer= false;
1047
1048    // video unique identifier
1049    var _vuid = 0;
1050    // content unique identifier
1051    var _cuid = 0;
1052    var _puid = 0;
1053    var _liveId = 0;
1054    var _ratingLevelImage = '';
1055
1056    var _playLast = 0;
1057    var _groupId = 0;
1058    var _groupLast = {};
1059    var _playbackActive = false;
1060    var _playbackList = null;
1061    var _playbackLanguage = null;
1062    var _playbackSubtitle = null;
1063    var _playerConfigLanguage = null;
1064    var _playerConfigSubtitle = null;
1065    var _isLimitReach = false;
1066    var _limitReachReason = null;
1067    var _videoType = null;
1068    var _programType = null;
1069    var _recommendationsLoaded = false;
1070    var _recommendationsShown = false;
1071    var _recommendationsForceClose = false;
1072    var _nextSeasonEpisodeLoaded = false;
1073    var _nextSeasonEpisodeShown = false;
1074    var _nextSeasonEpisodeForceClose = false;
1075    var _timeCodes = null;
1076    var _timeCodeIntro = null;
1077    var _skipIntroShown = false;
1078    var _timeCodeOutro = null;
1079    var _skipOutroShown = false;
1080    var _prerollShown = false;
1081    var _avodShown = false;
1082    var _isCasting = false;
1083    var _pauseOnHide = false;
1084
1085    // Set name of hidden property and visibility change event
1086    // since some browsers only offer vendor-prefixed support
1087    var _hidden;
1088    var _visibilityChange;
1089
1090    var _button = null;
1091
1092    var PLAYER;
1093    var ERROR_CODE;
1094
1095    var _isModalHide = true;
1096    var _sourceLoaded = false;
1097
1098    playerElement.on('click', '#player-recommendations-close', function(){
1099      hidePlayerRecommendations(true);
1100    });
1101
1102    playerElement.on('click', '#player-next-season-episode-close', function(){
1103      hidePlayerNextSeasonEpisode(true);
1104    });
1105
1106    var player = function () {
1107      return player;
1108    }
1109
1110    $('#player-skip-intro').on('click', function() {
1111      skipIntro();
1112    });
1113
1114    $('#player-skip-outro').on('click', function() {
1115      hidePlayerSkipOutro();
1116      nextEpisode(currentEpisode);
1117    });
1118
1119    var init = function () {
1120      ERROR_CODE        = bitmovin.player.ErrorCode;
1121      if (! isEpisode()) {
1122        initPlaybackData();
1123      }
1124    }
1125
1126    var onReady = function () {
1127
1128      if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
1129        _hidden = "hidden";
1130        _visibilityChange = "visibilitychange";
1131      } else if (typeof document.msHidden !== "undefined") {
1132        _hidden = "msHidden";
1133        _visibilityChange = "msvisibilitychange";
1134      } else if (typeof document.webkitHidden !== "undefined") {
1135        _hidden = "webkitHidden";
1136        _visibilityChange = "webkitvisibilitychange";
1137      }
1138      // Set event
1139      setEventFocusBack();
1140
1141      var serverUrl       = '';
1142
1143      var _currentSource = {};
1144      var _streamStarted  = false;
1145      var _restartCount   = 0;
1146      var _restartLimit   = 3;
1147      var _triggerPlayOnRestart = false;
1148      var _currentLoaderText = 0;
1149      var _sourceQualityBitrate = null;
1150      var _refreshBySeconds = 2;
1151
1152      OPConfig.setLimitations(limitations);
1153
1154      if($("link[rel='canonical']").length) {
1155        serverUrl = $("link[rel='canonical']").attr("href");
1156      }
1157
1158      OPDebug.setDebug();
1159
1160      var uiConfig = {os : OPHelpers.getOsName()};
1161
1162      var container = document.getElementById('player-drm');
1163      var playerConfig = OPConfig.getConfig();
1164
1165      player = new bitmovin.player.Player(container, playerConfig);
1166
1167      try {
1168        bitmovin.playerui.UIManager.setLocalizationConfig({ language: locale, browserLanguageDetection: false });
1169        uiManager = new bitmovin.playerui.UIFactory.buildDefaultUI(player, uiConfig);
1170      } catch(e) {
1171        console.error(e);
1172      }
1173
1174      try {
1175        // bind the keyboard control to the player instance
1176        new PlayerKeyboardControl(player, true);
1177      } catch(e) {
1178        console.warn(e);
1179      }
1180
1181      OPDebug.setDrm(player);
1182
1183      /**
1184       * Emitted when the player is ready for immediate playback, because initial audio/video
1185       * has been downloaded.
1186       */
1187      player.on(bitmovin.player.PlayerEvent.Ready, function (data) {
1188        OPDebug.log('PlayerEvent.Ready');
1189        if (isVideoTypeFeature()) {
1190          if (_seekTime && _seekTime > 0) {
1191            player.seek(_seekTime);
1192          }
1193          setTimeCodes();
1194        }
1195        trackingVideoView(_vuid);
1196        setSettingsPanelUI();
1197      });
1198
1199      player.on(bitmovin.player.PlayerEvent.SegmentPlayback, function (data) {
1200        if (data.mimeType == 'video/mp4') {
1201          Playback.saveCurrentData(player)
1202        }
1203        resetLoaderText();
1204      });
1205
1206      player.on(bitmovin.player.PlayerEvent.PlaybackFinished, function () {
1207        OPDebug.log(' --> Event player // onPlaybackFinished');
1208        onPlaybackFinished();
1209      });
1210
1211      player.on(bitmovin.player.PlayerEvent.Error, function (data) {
1212        onPlayerError(data);
1213      });
1214
1215      player.on(bitmovin.player.PlayerEvent.Play, function () {
1216        OPDebug.log('PlayerEvent.Play');
1217        // when modal is hide, do not authorize to play content
1218        if (_isModalHide || (_prerollShown && ! _avodShown)) {
1219          player.pause()
1220        }
1221
1222      });
1223
1224      player.on(bitmovin.player.PlayerEvent.Paused, function () {
1225        OPDebug.log('PlayerEvent.Paused');
1226        Playback.saveCurrentData(player, true);
1227      });
1228
1229      player.on(bitmovin.player.PlayerEvent.Playing, function () {
1230        OPDebug.log('PlayerEvent.Playing');
1231        toggleFullScreen();
1232        _prerollShown = false;
1233      });
1234
1235      player.on(bitmovin.player.PlayerEvent.AudioChanged, function(event){
1236        OPHelpers.setAudioPreferences(event.targetAudio.lang)
1237        if (_sourceLoaded) {
1238          Playback.saveCurrentData(player, true)
1239        }
1240      })
1241
1242      player.on(bitmovin.player.PlayerEvent.SubtitleDisabled, function(event){
1243        OPSource.setCurrentSubtitle(event.subtitle.lang, event.type)
1244        OPHelpers.setSubtitlesPreferences('')
1245        if (_sourceLoaded) {
1246          Playback.saveCurrentData(player, true)
1247        }
1248      })
1249
1250
1251      player.on(bitmovin.player.PlayerEvent.SubtitleEnabled, function(event){
1252        OPSource.setCurrentSubtitle(event.subtitle.lang, event.type)
1253        OPHelpers.setSubtitlesPreferences(event.subtitle.lang)
1254        if (_sourceLoaded) {
1255          Playback.saveCurrentData(player, true)
1256        }
1257      })
1258
1259      player.on(bitmovin.player.PlayerEvent.SourceLoaded, function(event){
1260        _sourceLoaded = true;
1261      })
1262
1263      player.on(bitmovin.player.PlayerEvent.StallStarted, function(event){
1264        console.debug('=== StallStarted : ' + _nextTime);
1265        console.debug('bitrate before vs after', player.getVideoQuality().bitrate, player.getDownloadedVideoData().bitrate);
1266        console.debug('bitrate before vs after', _sourceQualityBitrate, player.getDownloadedVideoData().bitrate);
1267
1268        if (_endOfStream == true) {
1269          $('.bmpui-ui-errormessage-overlay').addClass('bmpui-hidden');
1270          console.debug('=== StallStarted && _endOfStream : return' );
1271          return;
1272        }
1273
1274        if (player.getVideoQuality().bitrate !== null) {
1275          var ratio = player.getVideoQuality().bitrate / player.getDownloadedVideoData().bitrate;
1276        } else if(_sourceQualityBitrate !== null && _sourceQualityBitrate != '') {
1277          var ratio = _sourceQualityBitrate / player.getDownloadedVideoData().bitrate;
1278        } else {
1279          var ratio = 1;
1280        }
1281
1282        var newNextTime = _nextTime * ratio;
1283        console.debug('_nextTime by ratio | chunk download time', newNextTime);
1284        if (newNextTime < 2) {
1285          newNextTime = 0;
1286        }
1287
1288        if (newNextTime > 0 && _startTime > 0) {
1289          var diffTime = (Date.now() - _startTime)/1000;
1290          //Convert diff time in percentage
1291          startBuffer = (100 * diffTime) / newNextTime ;
1292
1293          if (startBuffer > 90) {
1294            resetLoaderText();
1295          } else {
1296            setLoaderText(startBuffer);
1297          }
1298        } else {
1299          resetLoaderText();
1300        }
1301
1302        clearInterval(bufferVideoItem);
1303
1304        bufferVideoItem = window.setInterval(function(){
1305          if (getCurrentLoader() > 90) {
1306            addToLoaderText(1);
1307          } else if(newNextTime <= 0) {
1308            addToLoaderText(2);
1309          } else {
1310            var newLoaderText = (100 / newNextTime)/_refreshBySeconds;
1311            addToLoaderText(newLoaderText);
1312          }
1313        }, 1000/_refreshBySeconds);
1314
1315      })
1316
1317      player.on(bitmovin.player.PlayerEvent.StallEnded, function(event){
1318        console.debug('=== StallEnded ===')
1319        clearInterval(bufferVideoItem);
1320        OPDebug.setStalledTime(player);
1321      })
1322
1323      player.on(bitmovin.player.PlayerEvent.SubtitleAdded, function (event) {
1324        removeDuplicateSubtitle(event)
1325        setSettingsPanelUI();
1326      })
1327
1328      player.on(bitmovin.player.PlayerEvent.DownloadFinished, function(data) {
1329        var estimatedBandwith = OPBandwith.calculate(data.size, data.downloadTime);
1330
1331        switch(data.downloadType) {
1332          case 'manifest/dash':
1333          case 'manifest/hls/variant':
1334          case 'manifest/hls/master':
1335          case 'manifest/smooth':
1336          case 'drm/license/widevine':
1337          case 'media/audio':
1338          case 'media/subtitles':
1339            break;
1340
1341          case 'media/video':
1342            OPBandwith.save(estimatedBandwith);
1343            OPLatency.save(data.timeToFirstByte);
1344
1345            if (data.size > 10000) {
1346              OPChunk.save(data.size, player.getDownloadedVideoData().bitrate);
1347
1348              estimatedSize = (player.getDownloadedVideoData().bitrate * OPChunk.getDuration()) / 8;
1349
1350              _nextTime = OPChunk.getDownloadTime(OPBandwith.getMinFromLast(3), estimatedSize) + data.timeToFirstByte;
1351
1352              addToLoaderText(4);
1353            }
1354            break;
1355
1356          default:
1357            console.debug('DownloadType unkonwn : ' + data.downloadType)
1358            break;
1359        }
1360
1361        if (data.downloadType.indexOf('media') !== -1 && data.mimeType.indexOf('video') !== -1 && data.size > 1000) {
1362          OPDebug.setModalDebugInfos('current', getLabelPlaybackVideo());
1363          OPDebug.setModalDebugInfos('bandwidth', estimatedBandwith + 'kbps');
1364        }
1365
1366        OPDebug.setLatency();
1367
1368        /**
1369         * Hack for last chunk missing
1370         **/
1371        timeLeft = player.getSeekableRange().end - player.getCurrentTime();
1372
1373        if (timeLeft < 20) {
1374          player.buffer.setTargetLevel('forwardduration', 4, 'audio');
1375        } else if (timeLeft < 60) {
1376          player.buffer.setTargetLevel('forwardduration', 10, 'audio');
1377        }
1378
1379        if (timeLeft < 30 && data.downloadType == 'media/audio' && data.httpStatus == 403 && data.success == false) {
1380          console.debug('=== Start Hack for last chunk missing ===');
1381          _endOfStream = true;
1382          player.pause;
1383          player.unload()
1384            .then(function(json) { /* traiter le JSON */ })
1385            .catch(function(error) {
1386                OPDebug.log(error);
1387               /* La ligne précédent peut aussi déclencher une
1388                  erreur (si console vaut {} par exemple) */
1389             })
1390            .finally(function() {
1391              onPlaybackFinished();
1392            });
1393
1394          return;
1395        }
1396      })
1397
1398      player.on(bitmovin.player.PlayerEvent.Seek, function(event){
1399        OPDebug.log('PlayerEvent.Seek');
1400        console.debug('=== Seek ===')
1401        $(overlayLoader).show();
1402        onTimeChanged(event);
1403        resetLoaderText();
1404      })
1405
1406      player.on(bitmovin.player.PlayerEvent.Seeked, function(event){
1407        OPDebug.log('PlayerEvent.Seeked');
1408        $(overlayLoader).hide();
1409        Playback.saveCurrentData(player, true)
1410      })
1411
1412      // Is fired when the displayed video quality changed
1413      player.on(bitmovin.player.PlayerEvent.VideoPlaybackQualityChanged, function(event){
1414        _sourceQualityBitrate = event.sourceQuality.bitrate;
1415      })
1416
1417      player.on(bitmovin.player.PlayerEvent.Warning, function(event){
1418        if (player.getSeekableRange().end - player.getCurrentTime() < 20 && event.code == bitmovin.player.WarningCode.PLAYBACK_DECODE_RETRIES_EXCEEDED) {
1419          _endOfStream = true;
1420        }
1421      })
1422
1423      player.on(bitmovin.player.PlayerEvent.ViewModeChanged, function(event){
1424        console.debug('ViewModeChanged event', event);
1425        if ('orientation' in screen && event.from == 'fullscreen' && event.from == 'inline') {
1426          screen.orientation.unlock();
1427        }
1428      })
1429
1430      player.on(bitmovin.player.PlayerEvent.TimeChanged, function(event){
1431        onTimeChanged(event);
1432      })
1433
1434     player.on(bitmovin.player.PlayerEvent.AdBreakFinished, function (data) {
1435        _avodShown = false;
1436        console.log("Ad Break Finished:", data);
1437        initPrerollRatingLevel();
1438      });
1439
1440      player.on(bitmovin.player.PlayerEvent.AdBreakStarted, function (data) {
1441        _avodShown = false;
1442        console.log("Ad Break Started:", data);
1443      });
1444
1445      player.on(bitmovin.player.PlayerEvent.AdClicked, function (data) {
1446        console.log("Ad Clicked:", data);
1447      });
1448
1449      player.on(bitmovin.player.PlayerEvent.AdError, function (data) {
1450        _avodShown = false;
1451        console.log("Ad Error:", data);
1452      });
1453
1454      player.on(bitmovin.player.PlayerEvent.AdManifestLoaded, function (data) {
1455        _avodShown = true;
1456        console.log("Ad Manifest Loaded:", data);
1457      });
1458
1459      player.on(bitmovin.player.PlayerEvent.AdSkipped, function (data) {
1460        _avodShown = false;
1461        console.log("Ad Skipped:", data);
1462      });
1463
1464      player.on(bitmovin.player.PlayerEvent.AdStarted, function (data) {
1465        _avodShown = true;
1466
1467        if (hasPrerollRatingLevel()) {
1468          setPrerollRatingLevel();
1469        }
1470
1471        console.log("Ad Started:", data);
1472      });
1473
1474      player.on(bitmovin.player.PlayerEvent.AdFinished, function (data) {
1475        _avodShown = false;
1476      });
1477
1478      player.on(bitmovin.player.PlayerEvent.CastStarted, function(event){
1479        console.log('EVENT CAST STARTED');
1480        _isCasting = true;
1481        // OTTO-9766 - The cast does not get the correct audio
1482        const audio = player.getAudio();
1483        player.setAudio(audio.id);
1484      });
1485
1486      player.on(bitmovin.player.PlayerEvent.CastStopped, function(event){
1487        _isCasting = false;
1488      });
1489
1490      $('a[data-previous]').click(function(e){
1491        e.preventDefault();
1492        previousEpisode(currentEpisode);
1493      });
1494
1495      $('.bmpui-ui-previoustogglebutton').click(function(e){
1496        e.preventDefault();
1497        previousEpisode(currentEpisode);
1498      });
1499
1500      $('[data-next]').click(function(e){
1501        e.preventDefault();
1502        nextEpisode(currentEpisode);
1503      });
1504
1505      $('.bmpui-ui-nexttogglebutton').click(function(e){
1506        e.preventDefault();
1507        nextEpisode(currentEpisode);
1508      });
1509
1510      $(MODAL_PLAYER).on('show.bs.modal', function (event) {
1511        console.log('--> ' + MODAL_PLAYER  +'show.bs.modal')
1512        var button = $(event.relatedTarget);
1513        preLaunch(button);
1514      });
1515
1516      $(MODAL_PLAYER).on('hide.bs.modal', function (event) {
1517        _isModalHide = true;
1518        player.pause();
1519      });
1520
1521      // Replace default background-image color with the secondary color of the platform
1522      let backgroundUrl = $('.bmpui-ui-playbacktoggle-overlay .bmpui-image').css('background-image');
1523      let color = getComputedStyle(document.documentElement).getPropertyValue('--secondary').replace('#', '%23').trim();
1524      backgroundUrl = backgroundUrl.replace('secondary-color', color);
1525      $('.bmpui-ui-playbacktoggle-overlay .bmpui-image').css('background-image', backgroundUrl);
1526      _playbackActive = $('#is_playback_active').val() === '1';
1527      _programType = $('#program_type').val();
1528    }
1529
1530    function preLaunch(button) {
1531      if (_isLimitReach) {
1532        limitReach();
1533        return;
1534      }
1535
1536      _button = button;
1537
1538      _isModalHide = false;
1539
1540      initValuesFromElement(button);
1541
1542      if (isLive()) {
1543        _liveId = button.attr('data-live-id');
1544        launchLive(_liveId, _puid);
1545      } else {
1546        hidePlayerRecommendations();
1547        hidePlayerNextSeasonEpisode();
1548        launchVideo();
1549      }
1550    }
1551
1552    var initValuesFromElement = function (element) {
1553      _videoType = element.attr('data-video-type') ?? videoTypeFeature;
1554      currentModal = element.attr('data-target');
1555      _cuid = element.attr('data-cuid');
1556      _puid = element.attr('data-puid');
1557      _vuid = element.attr('data-vuid');
1558      _groupId = element.attr('data-group');
1559
1560      _prerollShown = false;
1561      hidePlayerSkipIntro();
1562      hidePlayerSkipOutro();
1563
1564      // Set title
1565      let dataTitle = element.attr('data-title');
1566      OPSource.setTitle(dataTitle);
1567
1568      // Set analytics title
1569      _analyticsTitle = dataTitle;
1570      OPSource.setAnalyticsTitle(_analyticsTitle);
1571      OPSource.setAnalyticsProgramId(_puid);
1572
1573      playerElement.addClass('program-type-' + _programType);
1574
1575      if (isLive()) {
1576        return ;
1577      }
1578
1579      initPlayerFavorite(_puid);
1580
1581      _playLast = element.attr('data-group-last') === '1';
1582
1583      if (isNaN(_groupId)) {
1584        _groupId = 0;
1585      }
1586      Playback.setGroupId(_groupId);
1587
1588      _ratingLevelImage = element.attr('data-rating-level');
1589
1590      if (! hasPrerollRatingLevel()) {
1591        OPSource.setPicture(element.attr('data-picture'));
1592      }
1593
1594      Playback.setVideoId(_vuid);
1595
1596      currentEpisode = null;
1597      if (isEpisode()) {
1598        if (! element.hasClass('link-play')) {
1599          currentEpisode = getEpisodeNumber(element);
1600          setSeriePlayButtonFromEpisode(element);
1601        } else {
1602          // The "resume" button (.link-play) points to the last played episode through
1603          // its data-puid but has no data-rank, so resolve currentEpisode from the
1604          // matching episode tile. Otherwise currentEpisode stays null and the
1605          // "next episode" button at the end of playback has no current rank to work from.
1606          var resumedEpisode = $('.video-episode[data-puid="' + _puid + '"]').first();
1607          if (resumedEpisode.length > 0) {
1608            currentEpisode = getEpisodeNumber(resumedEpisode);
1609          }
1610        }
1611        Playback.showPlaybacklabel($('.link-play'), currentEpisode);
1612      } else {
1613        Playback.showPlaybacklabel(element);
1614      }
1615
1616      Playback.enable(element);
1617
1618      if (_playLast && _groupId && _groupLast[_groupId] && _groupLast[_groupId].episode) {
1619        _seekTime = _groupLast[_groupId].duration;
1620      } else {
1621        const currentPlayback = Playback.getCurrent();
1622        if (currentPlayback === undefined) {
1623          _seekTime = 0;
1624        } else {
1625          _seekTime = currentPlayback && currentPlayback.duration ? currentPlayback.duration : 0;
1626        }
1627      }
1628
1629      if (_playLast && _groupId && _groupLast[_groupId] && _groupLast[_groupId].audio) {
1630        _playbackLanguage = _groupLast[_groupId].audio;
1631      } else {
1632        _playbackLanguage = Playback.getCurrent()?.audio ?? null;
1633      }
1634
1635      if (_playLast && _groupId && _groupLast[_groupId] && _groupLast[_groupId].subtitle) {
1636        _playbackSubtitle = _groupLast[_groupId].subtitle;
1637      } else {
1638        _playbackSubtitle = Playback.getCurrent()?.subtitle ?? null;
1639      }
1640
1641      _playerConfigLanguage = PlayerConfig.getAudio() ?? null;
1642      _playerConfigSubtitle = PlayerConfig.getSubtitles() ?? null;
1643
1644      console.debug('_groupLast[_groupId]', _groupLast[_groupId])
1645      console.debug('_playbackLanguage', _playbackLanguage);
1646      console.debug('_playbackSubtitle', _playbackSubtitle);
1647    }
1648
1649    var previousEpisode = function (currentNumber) {
1650      var previous = getPreviousEpisodeNumber(currentNumber);
1651      changeEpisode(previous);
1652    }
1653
1654    var nextEpisode = function (currentNumber) {
1655      var next = getNextEpisodeNumber(currentNumber);
1656      changeEpisode(next);
1657    }
1658
1659    /**
1660     * Change to a new episode from episode number
1661     *
1662     * @param  {[type]} newNumber [description]
1663     * @return {void}
1664     *
1665     * @author Jody JUSTINE <[email protected]>
1666     */
1667    var changeEpisode = function (newNumber) {
1668      OPDebug.log(' *-* changeEpisode');
1669
1670      var _item        = $('.video-episode[data-rank=' + newNumber + ']').first();
1671      var _modalName   = _item.attr('data-target');
1672      var _dataPicture = _item.attr('data-picture');
1673      initValuesFromElement(_item);
1674
1675      if (typeof _modalName == 'string') {
1676        _modalName = _modalName.trim();
1677      }
1678
1679      if (typeof _vuid !== "undefined" && _vuid != 0
1680        && typeof _modalName !== "undefined"&& _modalName != ''
1681        && typeof newNumber !== "undefined" && newNumber != '') {
1682
1683        currentEpisode = newNumber;
1684        currentModal   = _modalName;
1685
1686        OPSource.setPicture(_dataPicture);
1687
1688        launchVideo();
1689      } else if ((typeof _vuid == "undefined" || _vuid === 0 ) && typeof _modalName !== "undefined") {
1690        currentEpisode = null;
1691        $(MODAL_PLAYER).modal('hide');
1692        $(_modalName).modal('show');
1693      } else {
1694        $(MODAL_PLAYER).modal('hide');
1695      }
1696    }
1697
1698    function updateMinLimit(limit) {
1699        limit = Number(limit);
1700        if (!isNaN(limit)) {
1701            minLimit = {bitrate: limit};
1702            var availQ = player.getAvailableVideoQualities().filter(function (elem) {
1703                return elem.bitrate >= minLimit.bitrate;
1704            });
1705            if (availQ.length < 1) {
1706                console.debug('VAH: invalid limit, no quality left. Ingore limit');
1707                return;
1708            }
1709            minLimit.quality = availQ.reduce(function (a, b) {
1710                if (a.bitrate <= b.bitrate) {
1711                    return a;
1712                }
1713                return b;
1714            });
1715
1716            console.debug('VAH: updated min limit with ' + limit);
1717        }
1718    }
1719
1720    function updateMaxLimit(limit) {
1721        limit = Number(limit);
1722        if (!isNaN(limit)) {
1723            maxLimit = {bitrate: limit};
1724            var availQ = player.getAvailableVideoQualities().filter(function (elem) {
1725                return elem.bitrate <= maxLimit.bitrate;
1726            });
1727            if (availQ.length < 1) {
1728                console.debug('VAH: invalid limit, no quality left. Ignore limit');
1729                return;
1730            }
1731            maxLimit.quality = availQ.reduce(function (a, b) {
1732                if (a.bitrate > b.bitrate) {
1733                    return a;
1734                }
1735                return b;
1736            });
1737
1738            console.debug('VAH: updated max limit with ' + limit);
1739        }
1740    }
1741
1742    /**
1743     * [onPlaybackFinished description]
1744     * @return {[type]} [description]
1745     * @author Jody JUSTINE <[email protected]>
1746     */
1747    function onPlaybackFinished () {
1748      OPDebug.log('<=> onPlaybackFinished');
1749      $('.bmpui-ui-errormessage-overlay').addClass('bmpui-hidden');
1750
1751      Playback.saveCurrentData(player, true);
1752
1753      previous = INIT_PREVIOUS;
1754
1755      if (! currentEpisode || ! hasNextEpisode()) {
1756        player.setViewMode(bitmovin.player.ViewMode.Inline);
1757        console.debug('    No next episode / Return');
1758        if (OPHelpers.getOsName() !== OP.OS.IOS) {
1759          unloadPlayer();
1760        }
1761        $(MODAL_PLAYER).modal('hide');
1762        return;
1763      }
1764
1765      // Set the date we're counting down to
1766      var countDownDate = COUNT_DOWN_NUMBER;
1767
1768      $('.bmpui-ui-playbacktoggle-overlay').addClass('bmpui-hidden');
1769      $('.bmpui-container-wrapper').hide();
1770
1771      // Update the count down every 1 second
1772      var x = setInterval(function() {
1773
1774        countDownDate = countDownDate - 1;
1775
1776        $(overlayLoader).show();
1777        $('.overlay-loader .loader').hide();
1778        $('.overlay-loader .text').show();
1779
1780        // If the count down is over, write some text
1781        if (countDownDate <= 0) {
1782          $(".overlay-loader .text").text( "Début de la prochaine vidéo");
1783          countDownDate = COUNT_DOWN_NUMBER
1784
1785          clearInterval(x);
1786          nextEpisode(currentEpisode);
1787        } else {
1788          $(".overlay-loader .text").text( "Prochaine vidéo dans " + countDownDate);
1789        }
1790      }, 1000);
1791    }
1792
1793    /**
1794     * We display the recommendations in the player if :
1795     *
1796     * - the param "recommendation_player" is activated
1797     * - the video is type feature
1798     * - the video is not an episode OR the episode is the last one
1799     * - the current time reached the time in which we display the recommendations
1800     * - the user didn't close the recommendations block
1801     * Skip intro
1802     * Skip outro
1803     * Show recommendations
1804     *
1805     * @author Timothée Planchais <[email protected]>
1806     */
1807    function onTimeChanged(event)
1808    {
1809      if (isVideoTypeFeature()) {
1810        var currentTime = player.getCurrentTime();
1811        var durationTotal = player.getDuration();
1812
1813        var showRecommendationsAt = false;
1814        if (_timeCodeOutro !== null && _timeCodeOutro !== undefined) {
1815          showRecommendationsAt = _timeCodeOutro.start;
1816        } else if (seconds_before_end_to_display_recommendations > 0) {
1817          showRecommendationsAt = durationTotal - seconds_before_end_to_display_recommendations;
1818        }
1819
1820        /*
1821         * INTRO - Button skip intro
1822         */
1823        if (display_skip_intro_outro && _timeCodeIntro !== null) {
1824          skipIntroStartsAt = _timeCodeIntro.start;
1825          skipIntroEndsAt = _timeCodeIntro.end;
1826          if ( _avodShown ) {
1827            hidePlayerSkipIntro();
1828          } else if (! _prerollShown && ! _skipIntroShown && currentTime >= skipIntroStartsAt && currentTime < skipIntroEndsAt) {
1829            showPlayerSkipIntro();
1830          } else if(_skipIntroShown && (currentTime < skipIntroStartsAt || currentTime >= skipIntroEndsAt)) {
1831            hidePlayerSkipIntro();
1832          }
1833        }
1834
1835        /*
1836         * OUTRO - Button skip outro
1837         * Shown only on episodes, except the last episode
1838         */
1839        if (display_skip_intro_outro && hasNextEpisode() && showRecommendationsAt !== false) {
1840          if (! _skipOutroShown && !_skipIntroShown && currentTime >= showRecommendationsAt) {
1841            showPlayerSkipOutro();
1842          } else if(_skipIntroShown || (_skipOutroShown && currentTime < showRecommendationsAt)) {
1843            hidePlayerSkipOutro();
1844          }
1845        }
1846
1847        /**
1848         *  NEXT SEASON EPISODE
1849         *  We launch the next season episode if :
1850         *  - the param "display_next_season_episode" is activated
1851         *  - the serie has a next season
1852         *  - the current episode is the last one
1853         *  - we reach the time
1854         */
1855        else if(display_next_season_episode && hasNextSeason() && ! hasNextEpisode(false) && $('#player-next-season-episode').length > 0) {
1856          if (showRecommendationsAt !== false && currentTime >= showRecommendationsAt && ! _nextSeasonEpisodeShown && ! _nextSeasonEpisodeForceClose) {
1857            showPlayerNextSeasonEpisode();
1858          } else if(_nextSeasonEpisodeShown && currentTime < showRecommendationsAt) {
1859            hidePlayerNextSeasonEpisode();
1860          }
1861        }
1862
1863        /*
1864         * RECOMMENDATIONS
1865         * We display the recommendations in the player if :
1866         * - the param "recommendation_player" is activated
1867         * - the video is type feature
1868         * - the video is not an episode OR (the episode is the last of the serie)
1869         * - the current time reached the time in which we display the recommendations
1870         * - the user didn't close the recommendations block
1871         */
1872        else if (display_player_recommendations && (! isEpisode() || isLastSerieEpisode())) {
1873          if (showRecommendationsAt !== false && currentTime >= showRecommendationsAt && ! _recommendationsShown && ! _recommendationsForceClose) {
1874            initPlayerRecommendations();
1875            showPlayerRecommendations();
1876          } else if(_recommendationsShown && currentTime < showRecommendationsAt) {
1877            hidePlayerRecommendations();
1878          }
1879        }
1880      }
1881    }
1882
1883    /**
1884     * Move the recommendations to the player and initialize them
1885     *
1886     * @return void
1887     *
1888     * @author Timothée Planchais <[email protected]>
1889     */
1890    function initPlayerRecommendations()
1891    {
1892      if($('#player-recommendations').length == 0) {
1893        display_player_recommendations = false;
1894      }
1895
1896      if (display_player_recommendations && ! _recommendationsLoaded && $('#player-drm #player-recommendations').length == 0) {
1897        $('#player-recommendations').appendTo(playerHtmlId)
1898          .addClass('appended')
1899          .css({
1900            'display': '',
1901            'bottom': $('#player-drm .bmpui-ui-uicontainer:not(.bmpui-hidden) .bmpui-ui-controlbar').height()+'px'
1902          });
1903        _recommendationsLoaded = true;
1904      }
1905    }
1906
1907    /**
1908     * Show the recommendations in the player
1909     *
1910     * @return void
1911     *
1912     * @author Timothée Planchais <[email protected]>
1913     */
1914    function showPlayerRecommendations()
1915    {
1916      if ($('#player-drm #player-recommendations').length > 0) {
1917        _recommendationsShown = true;
1918        playerElement.addClass('show-recommendations');
1919      }
1920    }
1921
1922    /**
1923     * Hide the recommendations in the player
1924     *
1925     * @param bool force
1926     * @return void
1927     *
1928     * @author Timothée Planchais <[email protected]>
1929     */
1930    function hidePlayerRecommendations(force = false)
1931    {
1932      _recommendationsShown = false;
1933      playerElement.removeClass('show-recommendations');
1934      if (force) {
1935        _recommendationsForceClose = true;
1936      }
1937    }
1938
1939    /**
1940     * Show the skip intro button in the player
1941     *
1942     * @return void
1943     *
1944     * @author Timothée Planchais <[email protected]>
1945     */
1946    function showPlayerSkipIntro()
1947    {
1948      hidePlayerRecommendations();
1949      hidePlayerNextSeasonEpisode();
1950      _skipIntroShown = true;
1951      $('#player-skip-intro .btn-text').text(txt_skip_intro);
1952      $('#player-drm #player-skip-intro').css({'bottom': $('#player-drm .bmpui-ui-uicontainer:not(.bmpui-hidden) .bmpui-ui-controlbar').height()+'px'});
1953      playerElement.addClass('show-skip-intro');
1954    }
1955
1956    /**
1957     * Hide the skip intro button in the player
1958     *
1959     * @return void
1960     *
1961     * @author Timothée Planchais <[email protected]>
1962     */
1963    function hidePlayerSkipIntro()
1964    {
1965      _skipIntroShown = false;
1966      playerElement.removeClass('show-skip-intro');
1967    }
1968
1969    /**
1970     * Skip the intro, go to skipIntroEndsAt
1971     *
1972     * @return void
1973     *
1974     * @author Timothée Planchais <[email protected]>
1975     */
1976    function skipIntro()
1977    {
1978      player.seek(_timeCodeIntro.end);
1979    }
1980
1981    /**
1982     * Show the skip outro button in the player
1983     *
1984     * @return void
1985     *
1986     * @author Timothée Planchais <[email protected]>
1987     */
1988    function showPlayerSkipOutro()
1989    {
1990      _skipOutroShown = true;
1991      if (_programType === 'episode') {
1992        $('#player-skip-outro .btn-text').text(txt_next_episode);
1993      } else {
1994        $('#player-skip-outro .btn-text').text(txt_skip_outro);
1995      }
1996      $('#player-drm #player-skip-outro').css({'bottom': $('#player-drm .bmpui-ui-uicontainer:not(.bmpui-hidden) .bmpui-ui-controlbar').height()+'px'});
1997      playerElement.addClass('show-skip-outro');
1998    }
1999
2000    /**
2001     * Hide the skip outro button in the player
2002     *
2003     * @return void
2004     *
2005     * @author Timothée Planchais <[email protected]>
2006     */
2007    function hidePlayerSkipOutro()
2008    {
2009      _skipOutroShown = false;
2010      playerElement.removeClass('show-skip-outro');
2011    }
2012
2013  /**
2014   * Move the NextSeasonEpisode to the player and initialize it
2015   *
2016   * @return void
2017   *
2018   * @author Timothée Planchais <[email protected]>
2019   */
2020  function initPlayerNextSeasonEpisode()
2021  {
2022    if($('#player-next-season-episode').length == 0) {
2023      display_next_season_episode = false;
2024    }
2025
2026    if (display_next_season_episode && ! _nextSeasonEpisodeLoaded && $('#player-drm #player-next-season-episode').length == 0) {
2027      $('#player-next-season-episode').appendTo(playerHtmlId)
2028        .addClass('appended')
2029        .css({
2030          'display': ''
2031        });
2032      _nextSeasonEpisodeLoaded = true;
2033    }
2034  }
2035
2036  /**
2037   * Show the NextSeasonEpisode in the player
2038   *
2039   * @return void
2040   *
2041   * @author Timothée Planchais <[email protected]>
2042   */
2043  function showPlayerNextSeasonEpisode()
2044  {
2045    var elem = $('#player-drm #player-next-season-episode');
2046    if (elem.length > 0) {
2047      _nextSeasonEpisodeShown = true;
2048      playerElement.addClass('show-next-season-episode');
2049      elem.css({
2050        'bottom': $('#player-drm .bmpui-ui-uicontainer:not(.bmpui-hidden) .bmpui-ui-controlbar').height()+'px'
2051      })
2052    }
2053  }
2054
2055  /**
2056   * Hide the NextSeasonEpisode in the player
2057   *
2058   * @param force boolean
2059   * @return void
2060   *
2061   * @author Timothée Planchais <[email protected]>
2062   */
2063  function hidePlayerNextSeasonEpisode(force = false)
2064  {
2065    _nextSeasonEpisodeShown = false;
2066    playerElement.removeClass('show-next-season-episode');
2067    if (force) {
2068      _nextSeasonEpisodeForceClose = true;
2069    }
2070  }
2071
2072    /**
2073     * @return void
2074     *
2075     * @see https://bitmovin.com/docs/player/tutorials/how-to-add-timeline-markers
2076     * @author Timothée Planchais <[email protected]>
2077     */
2078    function setTimeCodes()
2079    {
2080      _timeCodes = null;
2081      _timeCodeIntro = null;
2082      _timeCodeOutro = null;
2083      if (savedPlayerData && 'time_codes' in savedPlayerData) {
2084        _timeCodes = savedPlayerData.time_codes;
2085        if (_timeCodes && _timeCodes.length > 0) {
2086          if (getTimeCode('intro') !== null && getTimeCode('intro') !== undefined) {
2087            _timeCodeIntro = getTimeCode('intro');
2088            if (_timeCodeIntro.btn_name && _timeCodeIntro.btn_name != '') {
2089              $('#player-drm #player-skip-intro .btn-text').text(_timeCodeIntro.btn_name);
2090            }
2091          }
2092          if (getTimeCode('outro') !== null && getTimeCode('outro') !== undefined) {
2093            _timeCodeOutro = getTimeCode('outro');
2094            if (_timeCodeOutro.btn_name && _timeCodeOutro.btn_name != '') {
2095              $('#player-drm #player-skip-outro .btn-text').text(_timeCodeOutro.btn_name);
2096            }
2097          }
2098
2099          var markers = [];
2100          _timeCodes.map(function(timeCode) {
2101            if (timeCode.type !== 'intro' && timeCode.type !== 'outro') {
2102              var marker = {
2103                time: timeCode.start,
2104                duration: 0,//timeCode.end && timeCode.end > timeCode.start ? timeCode.end - timeCode.start : 0,
2105                title: timeCode.title ?? null
2106              };
2107              uiManager.addTimelineMarker(marker);
2108            }
2109          });
2110
2111        }
2112      }
2113    }
2114
2115    /**
2116     * @param string type "intro"|"outro"
2117     * @return object
2118     *
2119     * @author Timothée Planchais <[email protected]>
2120     */
2121    function getTimeCode(type)
2122    {
2123      if (_timeCodes && _timeCodes.length > 0) {
2124        return _timeCodes.find(timeCode => timeCode.type === type);
2125      }
2126      return null;
2127    }
2128
2129    /**
2130     * Handle player error
2131     *
2132     * @param error object{name: string, code: string}
2133     * @return void
2134     *
2135     * @see  https://cdn.bitmovin.com/player/web/8/docs/enums/core_deficiency.errorcode.html
2136     * @uses 1000 - general errors | 2000 - extended use cases | 3000 - module errors
2137     */
2138    function onPlayerError (error) {
2139
2140      OPDebug.log('<=> onPlayerError ' + error.code)
2141
2142      var tech = currentTech != '' ? currentTech : player.getPlayerType() + '.' + player.getStreamType()
2143      OPDebug.log(tech, 'tech')
2144      OPDebug.log(currentTech, 'currentTech')
2145
2146      _errorHistory.push(error.code);
2147
2148      switch (error.code) {
2149
2150        case bitmovin.player.ErrorCode.UNKNOWN: // Error is unknown
2151        case bitmovin.player.ErrorCode.API_NOT_AVAILABLE:
2152        case bitmovin.player.ErrorCode.SOURCE_INVALID:
2153        case bitmovin.player.ErrorCode.VR_INCOMPATIBLE_PLAYER_TECHNOLOGY: // Player technology not compatible with VR playback
2154          showError(error.name, error.code)
2155          break;
2156
2157        case bitmovin.player.ErrorCode.SOURCE_STREAM_TYPE_NOT_SUPPORTED: // The stream type is not supported
2158        case bitmovin.player.ErrorCode.SOURCE_PROGRESSIVE_STREAM_ERROR: // Progressive stream type not supported or the stream has an error
2159          $(modalBadBrowser).modal('show')
2160          break;
2161
2162        case '007':
2163        case bitmovin.player.ErrorCode.DRM_NO_KEY_SYSTEM:
2164        case bitmovin.player.ErrorCode.DRM_FAILED_LICENSE_REQUEST:
2165        case bitmovin.player.ErrorCode.DRM_MEDIA_KEY_INITIALIZATION_FAILED:
2166        case bitmovin.player.ErrorCode.SOURCE_COULD_NOT_LOAD_MANIFEST: // The manifest could not be loaded
2167        case bitmovin.player.ErrorCode.SOURCE_ENCRYPTION_METHOD_NOT_SUPPORTED: // The encryption method is not supported
2168        case bitmovin.player.ErrorCode.SOURCE_FORCED_TECHNOLOGY_NOT_SUPPORTED: // The forced technology is not supported
2169        case bitmovin.player.ErrorCode.SOURCE_NO_SUPPORTED_TECHNOLOGY: // There was no technology detected to playback the provided source
2170          console.debug('=>>>>>> error.code', error);
2171          if (isTreatingError === false) {
2172            OPDebug.log('handleFailOver ('+ error.code +') =>')
2173            isTreatingError = true;
2174            handleFailOver(error)
2175          } else {
2176            OPDebug.log('ALREADY handleFailOver ! ('+ error.code +')')
2177          }
2178          break;
2179
2180        case bitmovin.player.ErrorCode.NETWORK_ERROR:
2181          if (_endOfStream == true) {
2182            $('.bmpui-ui-errormessage-overlay').addClass('bmpui-hidden');
2183          }
2184          break;
2185
2186        default:
2187          showError(error.name, error.code)
2188          break;
2189      }
2190
2191      return;
2192    }
2193
2194    /**
2195     * Get wanted video technology
2196     *
2197     * @return {string} wanted technology (ex: html5.hls)
2198     * @author Jody JUSTINE
2199     * @since  06/12/2019
2200     */
2201    function getWantedTech()
2202    {
2203      try {
2204        var retval = OPHelpers.getUrlParameter('wt')
2205        if (retval != '') {
2206          OPDebug.log('Wanted video technology (url) : ' + retval)
2207          return retval
2208        }
2209        OPDebug.log(wantedTech, 'Wanted video technology : ')
2210        return wantedTech
2211      } catch (error) {
2212        console.debug(error)
2213        return ''
2214      }
2215    }
2216
2217    /**
2218     * [launchVideo description]
2219     * @param  {[type]} button [description]
2220     * @return {[type]}        [description]
2221     */
2222    function launchVideo() {
2223      const videoId = _vuid;
2224      const programId = _puid;
2225      OPDebug.log('launchVideo ' + videoId);
2226      _endOfStream = false;
2227
2228      if (_isLimitReach) {
2229        limitReach();
2230        return;
2231      }
2232
2233      if(isVideoTypeFeature()){
2234        showEpisodesLinks();
2235      }
2236
2237      $('.bmpui-container-wrapper').show();
2238
2239      if (typeof videoId !== "undefined" && videoId != '') {
2240
2241        if (player.hasEnded() || previous != videoId || previous == -1) {
2242          previous = videoId;
2243
2244          OPDebug.log('launchVideo : first time')
2245
2246          resetTechTryList()
2247
2248          OPDebug.log('<=> UNLOAD launchVideo <=>')
2249          player.unload()
2250            .then(function(json) { /* traiter le JSON */ })
2251            .catch(function(error) {
2252                OPDebug.log(error);
2253               /* La ligne précédent peut aussi déclencher une
2254                  erreur (si console vaut {} par exemple) */
2255             })
2256            .finally(function() {
2257              //isLoading = false;
2258            });
2259
2260          $(overlayLoader).show();
2261          $('.overlay-loader .loader').show();
2262          $('.overlay-loader .text').hide();
2263
2264          getDataAndSetPlayer(videoId, programId);
2265
2266          pushDataLayerVideoEvent(isVideoTypeTrailer() ? videoTypeTrailer : videoTypeMain);
2267
2268          pushDataLayerEvent({
2269            'event': gtmEvents.VIDEO_PLAY
2270          }, true, 800);
2271
2272        } else if(_getDataAndSetPlayer === true) {
2273          console.log('Video player is getting data, play will start when all data will be loaded.');
2274        } else {
2275          OPDebug.log('launchVideo : reprise')
2276          console.log('play launchVideo : reprise')
2277          player.play().then(
2278            function(data) {
2279              // player.play();
2280              OPDebug.log('About to play bitmovin player instance');
2281            },
2282            function(reason) {
2283              //error
2284              OPDebug.log('error while about to play bitmovin player instance');
2285              OPDebug.log(reason);
2286
2287              OPDebug.log(reason)
2288              showError(reason)
2289            }
2290          );
2291        }
2292      } else {
2293        showError('Cannot play')
2294        OPDebug.log('launchVideo : nothing to play')
2295        player.unload()
2296      }
2297    }
2298
2299    function launchLive(liveId, programId) {
2300
2301      _endOfStream = false;
2302
2303      if (_isLimitReach) {
2304        limitReach();
2305        return;
2306      }
2307
2308      $('.bmpui-container-wrapper').show();
2309
2310      if (typeof liveId !== undefined && liveId !== '') {
2311        if (player.hasEnded() || previous != liveId || previous == -1) {
2312          previous = liveId;
2313          player.unload()
2314            .then(function(json) {
2315
2316            })
2317            .catch(function(error) {
2318                OPDebug.log(error);
2319             })
2320            .finally(function() {
2321
2322            });
2323
2324          $(overlayLoader).show();
2325          $('.overlay-loader .loader').show();
2326          $('.overlay-loader .text').hide();
2327
2328          getDataAndSetPlayer(liveId, programId);
2329
2330          pushDataLayerVideoEvent('live');
2331
2332        } else {
2333          player.play().then(
2334            function(data) {
2335              // player.play();
2336            },
2337            function(reason) {
2338              //error
2339              showError(reason);
2340            }
2341          );
2342        }
2343      } else {
2344        showError('Cannot play');
2345        player.unload();
2346      }
2347    }
2348
2349    /**
2350     * [setPlayer description]
2351     * @param {[type]} myData [description]
2352     */
2353    function setPlayer(myData) {
2354      _currentSource = OPSource.getSourceData(myData)
2355
2356      try {
2357        var audiotracksDetails = _currentSource.audiotracksDetails;
2358        var mainLanguage = _currentSource.mainLanguage;
2359        _pauseOnHide = _currentSource.pauseOnHide;
2360        delete _currentSource.audiotracksDetails;
2361        delete _currentSource.mainLanguage;
2362      } catch (e) {
2363        console.log(e);
2364      }
2365
2366      try {
2367
2368        setLoaderText(5);
2369
2370        OPDebug.setTech(player);
2371
2372        setLoaderText(7);
2373
2374        try {
2375          player.buffer.setTargetLevel(bitmovin.player.BufferType.ForwardDuration, 11, bitmovin.player.MediaType.Video)
2376        } catch (e) {
2377          console.warn('cannot set TargetLevel')
2378          console.warn(e)
2379        }
2380
2381        var forceTechnology = getWantedTech()
2382
2383        if (forceTechnology != '') {
2384          console.debug('forceTechnology : ' + forceTechnology)
2385          currentTech = forceTechnology;
2386        } else {
2387          currentTech = getLabelStreamType();
2388        }
2389
2390        isTreatingError = false;
2391
2392        player.load(_currentSource, forceTechnology)
2393          .then(function (data) {
2394            if (player.ads && myData.avod_roll) {
2395              OPDebug.log('schedule ads', myData.avod_roll)
2396              player.ads.schedule({
2397                tag: {
2398                  url: myData.avod_roll,
2399                  type: 'vast'
2400                },
2401                id: 'Ad',
2402                position: 'pre',
2403              });
2404            } else {
2405              initPrerollRatingLevel();
2406            }
2407
2408            console.debug('load then =>');
2409
2410            _getFirstDuration();
2411
2412            setLoaderText(20);
2413
2414            try {
2415              var playerConfigAudio = getPlayerConfigAudioId(audiotracksDetails);
2416              var playerConfigSubtitle = getPlayerConfigSubtitleId();
2417              var preferedAudio = OPHelpers.getAudioPreferences();
2418              var preferedSubs = OPHelpers.getSubtitlesPreferences();
2419              var defaultAudio = getDefaultAudio(player, mainLanguage);
2420              var playbackAudioId;
2421
2422              if (_playbackLanguage) {
2423                  playbackAudioId = getPlaybackAudioId();
2424              }
2425
2426              // First set audio from playback, then player config, then storage, then default
2427              if (playbackAudioId) {
2428                player.setAudio(playbackAudioId);
2429              } else if (playerConfigAudio) {
2430                 player.setAudio(playerConfigAudio);
2431              } else if (preferedAudio !== '') {
2432                player.setAudio(preferedAudio);
2433              } else if(defaultAudio.audio){
2434                player.setAudio(defaultAudio.audio);
2435              }
2436
2437              var subtitleId = null;
2438
2439              // First set subtitle from playback, then player config
2440              if (_playbackSubtitle) {
2441                subtitleId = getPlaybackSubtitleId();
2442              } else if (playerConfigSubtitle) {
2443                subtitleId = playerConfigSubtitle;
2444              }
2445
2446              //If playback and subtitles settings disabled
2447              if (_playbackLanguage && subtitleId == null) {
2448                disableAllSubtitles();
2449              }
2450              else if (subtitleId) {
2451                player.subtitles.enable(subtitleId, !0);
2452              } else if (preferedSubs !== '') {
2453                player.subtitles.enable(preferedSubs, !0)
2454              } else if(defaultAudio.subtitle){
2455                player.subtitles.enable(defaultAudio.subtitle, !0)
2456              }
2457            } catch(e) {
2458              console.error('cannot set audio&subtitle')
2459              console.error(e)
2460            }
2461
2462
2463            setLoaderText(22);
2464
2465            // Set browser
2466            OPDebug.setBrowser();
2467            setLoaderText(40);
2468
2469            //Set streamtype
2470            OPDebug.setModalDebugInfos('streamtype', getLabelStreamType());
2471
2472            availableRepresentations = availableQualities = player.getAvailableVideoQualities();
2473
2474            updateMinLimit(Number(300) * 1000);
2475            updateMaxLimit(Number(300000000000) * 1000);
2476
2477        }).catch(function (error) {
2478          console.debug('load catch =>')
2479
2480          OPDebug.log(' <->
2480 Error while loading (from catch)')
2481          OPDebug.log(error)
2482
2483          onPlayerError({code: '007'} );
2484        }).finally(function (error) {
2485          OPDebug.log('load finally =>')
2486          OPDebug.log('forceTechnology' + forceTechnology)
2487          OPDebug.log('getLabelStreamType' + getLabelStreamType())
2488
2489          currentTech = forceTechnology != '' ? forceTechnology : getLabelStreamType();
2490
2491          OPDebug.log('currentTech : ' + currentTech)
2492
2493          resetLoaderText();
2494        })
2495
2496      } catch (e) {
2497        console.debug('setPlayer catch =>')
2498        console.error(e)
2499        console.debug(player.getPlayerType() + '.' + player.getStreamType())
2500        console.debug(getLabelStreamType())
2501        showError(e)
2502      }
2503    }
2504
2505    /**
2506     * Get defautl audio
2507     *
2508     * @param  {[type]} playe        [description]
2509     * @param  {[type]} mainLanguage [description]
2510     * @return {[type]}              [description]
2511     * @author Thomas TECHER <[email protected]>
2512     */
2513    function getDefaultAudio(playe, mainLanguage) {
2514      var hasMainAudioLanguage = false;
2515
2516      var audios = player.getAvailableAudio();
2517      var subtitles = player.subtitles.list();
2518
2519      if (audios.length === 0) {
2520        return {'audio' : false, 'subtitle' : false};
2521      }
2522
2523      var audioId = audios[0].id;
2524      audios.forEach(function(audio){
2525        if(mainLanguage.includes(audio.lang)){
2526          hasMainAudioLanguage = true;
2527          audioId = audio.id;
2528        }
2529      });
2530
2531      if(hasMainAudioLanguage) {
2532        return {'audio' : audioId, 'subtitle' : false};
2533      } else {
2534        if (subtitles.length == 0) {
2535          return {'audio' : audioId, 'subtitle' : false};
2536        } else {
2537          var subtitleId = subtitles[0].id;
2538          subtitles.forEach(function(subtitle) {
2539            if (mainLanguage.includes(subtitle.lang)) {
2540              subtitleId = subtitle.id;
2541            }
2542          });
2543          return {'audio' : audioId, 'subtitle' : subtitleId};
2544        }
2545      }
2546    }
2547
2548    /**
2549     * Get the ID of the playback audio
2550     *
2551     * @return string
2552     * @author Timothée Planchais <[email protected]>
2553     */
2554    function getPlaybackAudioId() {
2555        var playbackAudioId = '';
2556        var audios = player.getAvailableAudio();
2557        if(audios && _playbackLanguage) {
2558            audios.forEach(function(audio){
2559              if(audio.lang == _playbackLanguage){
2560                  playbackAudioId = audio.id;
2561                  return ;
2562              }
2563            });
2564        }
2565
2566        return playbackAudioId;
2567    }
2568
2569    /**
2570     * Get the audio from the player config
2571     *
2572     * @params array
2573     * @return string
2574     * @author Florian PELLETIER <[email protected]>
2575     */
2576    function getPlayerConfigAudioId(audiotracksDetails) {
2577        var audio = '';
2578        var vo = '';
2579        var playerConfigAudioId;
2580        var audios = player.getAvailableAudio();
2581
2582        if (_playerConfigLanguage === 'qaa' && typeof audiotracksDetails == 'object') {
2583            for (const property in audiotracksDetails) {
2584                if (typeof audiotracksDetails[property].vo != 'undefined' && audiotracksDetails[property].vo) {
2585                  vo = audiotracksDetails[property].name;
2586                }
2587            }
2588        }
2589
2590        if (audios && _playerConfigLanguage) {
2591            audios.forEach(function(audio) {
2592                if (audio.lang == _playerConfigLanguage || (_playerConfigLanguage === 'qaa' && vo == audio.lang)) {
2593                    playerConfigAudioId = audio.id;
2594                    return ;
2595                }
2596            });
2597        }
2598
2599        return playerConfigAudioId;
2600    }
2601
2602    /**
2603     * Get the subtitles from the player config
2604     *
2605     * @return string
2606     * @author Florian PELLETIER <[email protected]>
2607     */
2608    function getPlayerConfigSubtitleId() {
2609      var playerConfigSubtitleId;
2610      var subtitles = player.subtitles.list();
2611
2612      if (subtitles && _playerConfigSubtitle) {
2613        subtitles.forEach(function (subtitle) {
2614          if (subtitle.lang == _playerConfigSubtitle) {
2615            playerConfigSubtitleId = subtitle.id;
2616            return;
2617          }
2618        });
2619      }
2620
2621      return playerConfigSubtitleId;
2622    }
2623
2624    /**
2625     * Get the ID of the playback subtitle
2626     *
2627     * @return string
2628     * @author Timothée Planchais <[email protected]>
2629     */
2630    function getPlaybackSubtitleId() {
2631      var playbackSubtitleId = null;
2632      var subtitles = player.subtitles.list();
2633      if(subtitles && _playbackSubtitle) {
2634        subtitles.forEach(function(subtitle){
2635          if(subtitle.lang == _playbackSubtitle){
2636            playbackSubtitleId = subtitle.id;
2637            return ;
2638          }
2639        });
2640      }
2641      return playbackSubtitleId;
2642    }
2643
2644    /**
2645     *
2646     * @return void
2647     * @author Timothée Planchais <[email protected]>
2648     */
2649    function disableAllSubtitles() {
2650      var subtitles = player.subtitles.list();
2651      if(subtitles) {
2652        subtitles.forEac
2652h(function(subtitle){
2653          player.subtitles.disable(subtitle.id);
2654        });
2655      }
2656    }
2657
2658    /**
2659     * Sort array
2660     * @author Hélène Telliez <[email protected]>
2661     */
2662    function dynamicSort(property) {
2663        var sortOrder = 1;
2664        if (property[0] === "-") {
2665            sortOrder = -1;
2666            property = property.substr(1);
2667        }
2668        return function (a,b) {
2669            var result = a[property] < b[property] ? -1 : (a[property] > b[property] ? 1 : 0);
2670            return result * sortOrder;
2671        }
2672    }
2673
2674    /**
2675     * Load external subtitles in web player only if trailer videos
2676     * @return void
2677     * @author Hélène Telliez <[email protected]>
2678     */
2679    function loadExternalSubtitles(savedPlayerData) {
2680        if ('subtitles' in savedPlayerData && typeof savedPlayerData.type !== 'undefined' && savedPlayerData.type === videoTypeTrailer) {
2681            savedPlayerData.subtitles.forEach(function (subtitle) {
2682                subtitle.label = OPHelpers.getLangTagName(subtitle);
2683            });
2684            savedPlayerData.subtitles = savedPlayerData.subtitles.sort(dynamicSort('label'));
2685        }
2686        // It will load the subtitles from the manifest
2687        else if ('subtitles' in savedPlayerData) {
2688            savedPlayerData.subtitles = [];
2689        }
2690    }
2691
2692    /**
2693     * Remove reloading subtitles if switch between videos
2694     * @return void
2695     * @author Hélène Telliez <[email protected]>
2696     */
2697    function removeDuplicateSubtitle(event) {
2698        if (event.subtitle.lang === 'unknown') {
2699            player.subtitles.remove(event.subtitle.id);
2700        }
2701    }
2702
2703    /**
2704     * Get data and load player
2705     *
2706     * @param  {integer} videoId Video id of the wanted video
2707     * @param {integer} programId Program id of the wanted video
2708     * @return void
2709     * @author Jody JUSTINE <[email protected]>
2710     */
2711    function getDataAndSetPlayer(videoId, programId) {
2712
2713      _getDataAndSetPlayer = true;
2714
2715      OPDebug.log('<=> getDataAndSetPlayer');
2716      $.ajax({
2717        type: 'POST',
2718        url:  "/api/player",
2719        data: {
2720            video_id: videoId,
2721            program_id: programId,
2722            program_type: _programType
2723        },
2724        beforeSend: function( xhr ) {
2725          xhr.overrideMimeType( "text/plain; charset=x-user-defined" );
2726        },
2727      })
2728      .done(function(data) {
2729        data = JSON.parse(data);
2730        savedPlayerData = data.data;
2731        if (_programType !== 'live') {
2732          loadExternalSubtitles(savedPlayerData);
2733          setTimeCodes(savedPlayerData);
2734        }
2735        setPlayer(savedPlayerData);
2736      })
2737      .fail(function(jqXHR) {
2738        if ((jqXHR.status ?? 0) == 419) {
2739          showError('Merci de vous reconnecter.');
2740        }
2741      })
2742      .always(function() {
2743        _getDataAndSetPlayer = false;
2744        $(overlayLoader).hide()
2745      });
2746
2747      return;
2748    }
2749
2750    /**
2751     * Add subtitles after player load/init
2752     * @param {object} myPlayer [description]
2753     * @param {object} myData   [description]
2754     * @author Jody JUSTINE <[email protected]>
2755     */
2756    function addSubtitles(myPlayer, myData) {
2757      try {
2758        if ('subtitles' in myData) {
2759          myData.subtitles.forEach(function(element) {
2760                try {
2761                    myPlayer.subtitles.add(element);
2762                } catch(error) {
2763                  console.error('adding subtitle failed');
2764                  console.debug(element);
2765                  console.error(error);
2766                }
2767          });
2768        }
2769      } catch(error) {
2770        console.error('adding subtitles source failed');
2771        console.error(error);
2772      }
2773    }
2774
2775    /**
2776     * Show episodes links under player
2777     * @return void
2778     */
2779    function showEpisodesLinks () {
2780      if (hasPreviousEpisode()) {
2781        $('.episode-buttons-previous').addClass('episode-buttons-show');
2782        $('.episode-buttons').addClass('episode-buttons-show');
2783        $('.bmpui-ui-previoustogglebutton').removeClass('bmpui-hidden');
2784      } else {
2785        $('.episode-buttons-previous').removeClass('episode-buttons-show');
2786        $('.bmpui-ui-previoustogglebutton').addClass('bmpui-hidden');
2787      }
2788      if (hasNextEpisode()) {
2789        $('.episode-buttons-next').addClass('episode-buttons-show');
2790        $('.episode-buttons').addClass('episode-buttons-show');
2791        $('.bmpui-ui-nexttogglebutton').removeClass('bmpui-hidden');
2792      } else {
2793        $('.episode-buttons-next').removeClass('episode-buttons-show');
2794        $('.bmpui-ui-nexttogglebutton').addClass('bmpui-hidden');
2795      }
2796      return;
2797    }
2798
2799    function hasPreviousEpisode () {
2800      return $('.video-episode[data-rank=' + getPreviousEpisodeNumber(currentEpisode) + ']').length > 0;
2801    }
2802
2803    function hasNextEpisode (checkWatchable = true) {
2804      return $('.video-episode[data-rank="' + getNextEpisodeNumber(currentEpisode) + '"]').length > 0;
2805    }
2806
2807    function hasNextSeason() {
2808      return $('#serie-seasons-list .serie-season-item.next-season').first().length > 0;
2809    }
2810
2811    function getPreviousEpisodeNumber (nb) {
2812      let prevNumber = 0;
2813      $($('.videos-item-link').get().reverse()).each(function(i) {
2814        var currentNumber = getEpisodeNumber($(this));
2815        if (currentNumber < nb && parseInt($(this).data('watchable')) === 1) {
2816          prevNumber = currentNumber;
2817          return false;
2818        }
2819      });
2820      return prevNumber;
2821    }
2822
2823    function getNextEpisodeNumber (nb, checkWatchable = true) {
2824      let nextNumber = 0;
2825      $('.video-episode').each(function(i) {
2826        var currentNumber = getEpisodeNumber($(this));
2827          if (currentNumber > nb && (! checkWatchable || parseInt($(this).data('watchable')) === 1)) {
2828            nextNumber = currentNumber;
2829            return false;
2830          }
2831        });
2832      return nextNumber;
2833    }
2834
2835    function isSerie()
2836    {
2837      return _programType === 'serie';
2838    }
2839
2840    function isSeason()
2841    {
2842      return _programType === 'season';
2843    }
2844
2845    function isEpisode()
2846    {
2847      return _programType === 'episode';
2848    }
2849
2850    function isLastSerieEpisode()
2851    {
2852      return isEpisode() && ! hasNextEpisode(false) && ! hasNextSeason();
2853    }
2854
2855    function isLive()
2856    {
2857      return _programType === 'live';
2858    }
2859
2860    function isVideoTypeFeature()
2861    {
2862      return _videoType === videoTypeFeature;
2863    }
2864
2865    function isVideoTypeTrailer()
2866    {
2867      return _videoType === videoTypeTrailer;
2868    }
2869
2870    function isVideoTypeBonus()
2871    {
2872      return _videoType === videoTypeBonus;
2873    }
2874
2875    /**
2876     * Reset technology tried list
2877     *
2878     * @return void
2879     *
2880     * @author Jody JUSTINE <[email protected]>
2881     */
2882    function resetTechTryList() {
2883      techTry = []
2884    }
2885
2886    /**
2887     * Handle fail over
2888     *
2889     * @return void
2890     *
2891     * @author Jody JUSTINE <[email protected]>
2892     * @since  25/10/2019
2893     */
2894    function handleFailOver() {
2895      techTry.push(currentTech)
2896      OPDebug.log('<=> UNLOAD handleFailOver <=>')
2897
2898      player.unload()
2899        .then(function(value) {
2900          console.debug('handleFailOver unload done')
2901        })
2902        .catch(function(error) {
2903          console.debug('handleFailOver unload catch')
2904          console.debug(error)
2905        })
2906        .finally(function() {
2907          console.debug('handleFailOver unload finally')
2908
2909          if (techTry.length > MAX_RETRY) {
2910            showError('maximum try reached')
2911          } else {
2912
2913            OPDebug.log(OPConfig.getWantedTech())
2914            retval = arr_diff(OPConfig.getWantedTech(), techTry)
2915            OPDebug.log('--> next try: ' + techTry.length + '/' + MAX_RETRY)
2916            OPDebug.log(techTry, 'tech already Try')
2917            OPDebug.log(retval, ' next tech is')
2918            wantedTech = retval[0]
2919            console.debug(' ==> setPlayer');
2920
2921            setPlayer(savedPlayerData)
2922          }
2923        })
2924    }
2925
2926    /**
2927     * Show error
2928     *
2929     * @return void
2930     *
2931     * @author Jody JUSTINE <[email protected]>
2932     * @since  25/10/2019
2933     */
2934    function showError(error, errorCode = null) {
2935      unloadPlayer(error, errorCode);
2936    }
2937
2938    /**
2939     * [getLabelPlaybackVideo description]
2940     * @return {[type]} [description]
2941     * @author Jody JUSTINE <[email protected]>
2942     */
2943    function getLabelPlaybackVideo() {
2944      return OPHelpers.getQualityLabels(player.getPlaybackVideoData()) + '<br />(' + player.getPlaybackVideoData().id + ' / ' +  player.getPlaybackVideoData().bitrate / 1000 + ')';
2945    }
2946
2947
2948    function geLabelSuggested(data) {
2949      return data.label + '<br /> (' + data.id + ' / ' + data.bitrate/1000 + ')';
2950    }
2951
2952    function getLabelStreamType() {
2953      return player.getPlayerType() + '.' + player.getStreamType();
2954    }
2955
2956    function getCurrentLoader() {
2957      return _currentLoaderText;
2958    }
2959
2960    function resetLoaderText() {
2961      setLoaderText(0);
2962    }
2963
2964    function addToLoaderText(nb) {
2965      setLoaderText(_currentLoaderText + nb);
2966    }
2967
2968
2969    function setLoaderText(nb) {
2970      OPDebug.log('setLoaderText => ' + nb);
2971
2972      try {
2973        _currentLoaderText = parseFloat(nb);
2974        if (_currentLoaderText > 100) {
2975          _currentLoaderText = 100;
2976        }
2977        var elOverlay = '.bmpui-ui-buffering-overlay';
2978        var elBrother = elOverlay + ' .bmpui-container-wrapper';
2979        var textClass = 'loader-text';
2980        var elText = '.' + textClass;
2981        var elLoader = elOverlay + ' ' + elText;
2982
2983        if (getCurrentLoader() == 0) {
2984          $(elOverlay).hide();
2985        } else if ($(elLoader).length == 0) {
2986          $(elOverlay).show();
2987          $('<span class="' + textClass +'">' + txt_loading + ':&nbsp;' + parseInt(_currentLoaderText) + '%</span>').insertAfter(elBrother);
2988        } else  {
2989          $(elOverlay).show();
2990          $(elLoader).text(txt_loading + ': ' +parseInt( _currentLoaderText) + '%');
2991        }
2992      } catch (e) {
2993        console.warn(e)
2994        console.warn('cannot set text loader')
2995      }
2996    }
2997
2998    var onResize = function () {
2999
3000    }
3001
3002    function toggleFullScreen() {
3003
3004      OPDebug.log('toggleFullScreen');
3005
3006      var orientation = screen.orientation    ||
3007                        screen.mozOrientation ||
3008                        screen.msOrientation  ||
3009                        null;
3010
3011      if (orientation === null) {
3012        OPDebug.log(' <> orientation: API not supported');
3013      } else if (typeof orientation === 'object') {
3014        OPDebug.log(' <> orientation: new versions supported');
3015      } else {
3016        OPDebug.log(' <> orientation: old versions supported');
3017      }
3018
3019      if (OPHelpers.getOsName() == OP.OS.ANDROID) {
3020
3021        OPDebug.log('Fullscreen ANDROID');
3022
3023        // ANDROID - if player not in fullscreen
3024        if (player.getViewMode() !== bitmovin.player.ViewMode.Fullscreen) {
3025          player.setViewMode(bitmovin.player.ViewMode.Fullscreen);
3026
3027          OPDebug.log('toggleFullScreen fullScreen after' + orientation)
3028
3029        try {
3030          screen.lockOrientationUniversal = screen.lockOrientation || screen.mozLockOrientation || screen.msLockOrientation;
3031          OPDebug.log(screen.lockOrientationUniversal);
3032          if (screen.lockOrientationUniversal.lock("landscape-secondary")) {
3033            OPDebug.log('Orientation was locked');
3034          } else {
3035            OPDebug.log('Orientation lock failed');
3036          }
3037        } catch (e) {
3038          OPDebug.log('cannot lockOrientationUniversal');
3039          console.error(e);
3040        }
3041
3042        try {
3043          screen.lockOrientationUniversal = screen.lockOrientation || screen.mozLockOrientation || screen.msLockOrientation;
3044          if (screen.lockOrientationUniversal(["landscape-primary", "landscape-secondary"])) {
3045            OPDebug.log('Orientation was locked');
3046          } else {
3047            OPDebug.log('Orientation lock failed');
3048          }
3049        } catch (e) {
3050          OPDebug.log('cannot lockOrientationUniversal');
3051          console.error(e);
3052        }
3053
3054          if (orientation === undefined) {
3055            OPDebug.log("The orientation API isn't supported in this browser.");
3056          } else if (orientation !== undefined) {
3057            OPDebug.log('Try to lock Fullscreen');
3058
3059
3060          try {
3061            if (screen.orientation) {
3062              OPDebug.log('screen.orientation');
3063              screen.orientation.lock('landscape-primary')
3064                .then(function(value) {
3065                  console.debug("Succès ! ");
3066                })
3067                .catch(function(error) {
3068                  OPDebug.debug('<> FULLSCREEN WARNING : cannot change orientation by js.')
3069                  OPDebug.debug(error);
3070
3071                });
3072
3073            }
3074
3075          } catch (e) {
3076            OPDebug.log('cannot lock screen.orientation');
3077            console.error(e);
3078          }
3079
3080          try {
3081            if (screen.msLockOrientation) {
3082              OPDebug.log('screen.msLockOrientation');
3083              screen.msLockOrientation.lock("landscape-primary");
3084            }
3085          } catch (e) {
3086            OPDebug.log('cannot lock screen.msLockOrientation');
3087            console.error(e);
3088          }
3089
3090          try {
3091            if (screen.mozLockOrientation) {
3092              OPDebug.log('screen.mozLockOrientation');
3093              screen.mozLockOrientation.lock("landscape-primary");
3094            } else {
3095              OPDebug.log('cannot lock screen.mozLockOrientation');
3096            }
3097          } catch (e) {
3098            console.error(e);
3099          }
3100
3101
3102          }
3103        } // end - ANDROID - if player not in fullscreen
3104
3105      } else {
3106        OPDebug.log('Fullscreen others')
3107      }
3108    }
3109
3110    /**
3111     * [videoAdaptationHandler description]
3112     * @param  {[type]} e [description]
3113     * @return {[type]}   [description]
3114     */
3115    function videoAdaptationHandler(e) {
3116      _startTime = Date.now();
3117
3118      var suggestedRep = availableRepresentations.filter(function(rep) {
3119        return rep.id === e.suggested;
3120      })[0]
3121
3122      if (suggestedRep) {
3123
3124        OPDebug.setModalDebugInfos('current', getLabelPlaybackVideo())
3125        OPDebug.setModalDebugInfos('suggested', geLabelSuggested(suggestedRep))
3126
3127        OPDebug.setFrames(player);
3128        OPDebug.setBuffer(player);
3129
3130        OPDebug.setModalDebugInfos('bitrate', player.getVideoQuality().id)
3131
3132        var wanted;
3133        var q = availableQualities[e.suggested];
3134        if (suggestedRep.bitrate < minLimit.quality.bitrate) {
3135            console.debug('VAH : bitrate too small, use the lowest qualith with bitrate >= minLimit : ' + OPHelpers.humanizeBitrate(suggestedRep.bitrate) + ' < ' + OPHelpers.humanizeBitrate(minLimit.quality.bitrate));
3136            console.debug('VAH :' + minLimit.quality.bitrate);
3137            wanted = minLimit.quality.id;
3138        } else if (suggestedRep.bitrate > maxLimit.quality.bitrate) {
3139            console.debug('VAH : bitrate too high, use the highest quality with bitrate <= maxLimit : ' + OPHelpers.humanizeBitrate(suggestedRep.bitrate) + ' < ' + OPHelpers.humanizeBitrate(maxLimit.quality.bitrate));
3140            console.debug('VAH :' + maxLimit.quality.bitrate);
3141            wanted  = maxLimit.quality.id;
3142        } else if(lastSuggested == suggestedRep.bitrate) {
3143          // Nothing to do && No log to display
3144        } else {
3145            console.debug('VAH : use the automatically selected quality : ' + e.suggested + ' | ' + OPHelpers.humanizeBitrate(minLimit.quality.bitrate) + ' < ' + OPHelpers.humanizeBitrate(suggestedRep.bitrate) + ' < ' + OPHelpers.humanizeBitrate(maxLimit.quality.bitrate));
3146            wanted = e.suggested;
3147        }
3148
3149        lastSuggested = suggestedRep.bitrate;
3150        return wanted;
3151
3152      } else {
3153        console.debug('VAH : no suggested quality');
3154      }
3155      // Returns the currently selected video quality. One of the elements of getAvailableVideoQualities.
3156    }
3157
3158    /**
3159     * Set First chunk duration on debug modal
3160     * @returnvoid
3161     */
3162    function _getFirstDuration() {
3163      try {
3164        var availableSegments = player.getAvailableSegments();
3165        var videoMp4 = availableSegments["video/mp4"];
3166
3167        if (videoMp4) {
3168          var videoMp4First = videoMp4[Object.keys(videoMp4)[0]];
3169
3170          OPChunk.setDuration(videoMp4First[0].duration);
3171          OPDebug.setModalDebugInfos('chunk', OPChunk.getDuration())
3172        }
3173      } catch (e) {
3174        console.warn(e)
3175      }
3176    }
3177
3178    /**
3179     * Autoplay a serie/season page reached through a watch deep link (OTTO-15311).
3180     *
3181     * /watch/{programId} redirects here with ?playback=1, so this only
3182     * ever runs for a deep link: a regular visit has no `launchPlayback` and is untouched.
3183     *
3184     * The episode to play is resolved by the mechanisms already in place rather than by a
3185     * second, competing implementation :
3186     *   - ?episode={id} (an episode deep link) marks the requested episode `.active`;
3187     *   - otherwise togglePlaybackLabels() has pointed .link-play at the episode to resume,
3188     *     or setSeriePlayButton() at the first episode when there is nothing to resume.
3189     *
3190     * The two are never mixed : when a precise episode is requested but absent from the
3191     * loaded page, nothing is launched rather than the resume episode.
3192     *
3193     * Fires at most once, and only clicks a button the user could have clicked: when the
3194     * browser refuses the resulting playback, the player stays ready behind one interaction
3195     * exactly as it does for a manual click.
3196     *
3197     * @param {boolean} allowResumeButton Fall back to .link-play (resume/first episode).
3198     *                                    False before the playback data is loaded, when
3199     *                                    that button has not been resolved yet.
3200     * @return {boolean} Whether a player was opened.
3201     */
3202    function launchDeepLinkPlayback(allowResumeButton) {
3203      if (! isEpisode() || ! launchPlayback) {
3204        return false;
3205      }
3206
3207      // Last-chance call: disarm now, whatever the outcome. initPlaybackData() is re-run on
3208      // every `content-changed` event (season switch, show more, pagination), and a flag
3209      // left armed would fire the player later, on a user action that did not ask for it.
3210      if (allowResumeButton) {
3211        launchPlayback = false;
3212      }
3213
3214      // An episode deep link asks for one precise episode. `.active` is set by the blade
3215      // from `$currentEpisodeId === $episode->episode_id`, i.e. the same comparison the url
3216      // expresses, so it is the authoritative match.
3217      const requestedEpisode = $('.videos-item-episode.active .video-episode').first();
3218
3219      if (requestedEpisode.length) {
3220        launchPlayback = false;
3221        requestedEpisode.click();
3222
3223        return true;
3224      }
3225
3226      // The requested episode is not on the loaded page — the swiper layout does not
3227      // paginate to it. Substituting the resume episode would silently play something else
3228      // than the link asked for, so nothing is launched: the player stays one click away.
3229      if (hasRequestedEpisode()) {
3230        console.debug('Deep link playback : requested episode not on the page');
3231
3232        return false;
3233      }
3234
3235      if (! allowResumeButton) {
3236        return false;
3237      }
3238
3239      const resumeButton = $('.link-play:visible').first();
3240
3241      if (! resumeButton.length) {
3242        console.debug('Deep link playback : no episode to play');
3243
3244        return false;
3245      }
3246
3247      launchPlayback = false;
3248      resumeButton.click();
3249
3250      return true;
3251    }
3252
3253    /**
3254     * Whether the url asks for one precise episode (?episode={id}), which forbids falling
3255     * back to the resume/first episode.
3256     */
3257    function hasRequestedEpisode() {
3258      return new URLSearchParams(window.location.search).has('episode');
3259    }
3260
3261    function initPlaybackData() {
3262
3263      if (isEpisode() && launchFirstEpisode) {
3264        launchFirstEpisode = false;
3265        $('.section-episodes .video-episode').first().click();
3266      }
3267
3268      // An episode deep link targets one precise episode, already marked .active by the
3269      // blade: no need to wait for the playback data to resolve which one to play.
3270      launchDeepLinkPlayback(false);
3271
3272      if (! _playbackActive) {
3273        console.debug('Playback disabled');
3274
3275        // No playback data to resume from, so the last-resort resolution applies right
3276        // away: .link-play points at the first episode (setSeriePlayButton).
3277        launchDeepLinkPlayback(true);
3278
3279        return;
3280      }
3281
3282      let programIds = [];
3283      let groupVideo = [];
3284      let groupVuid = [];
3285      let groupEpisode = [];
3286      let groupLastEpisode = [];
3287      $('[data-puid][data-vuid]').each(function(element){
3288          let puid = parseInt($( this ).attr("data-puid"), 10);
3289          let groupId = parseInt($( this ).attr('data-group'), 10);
3290
3291          if (! programIds.includes(puid))  {
3292            programIds.push(puid);
3293          }
3294
3295          var episodeNumber = getEpisodeNumber($( this ));
3296
3297          if (episodeNumber) {
3298            groupEpisode[puid] = episodeNumber;
3299            groupVuid[puid] = parseInt($( this ).attr('data-vuid'), 10);
3300
3301            if (groupId && episodeNumber) {
3302              groupVideo[groupId] === undefined ? groupVideo[groupId] = [] : null;
3303              groupVideo[groupId].push(puid);
3304              if (groupLastEpisode[groupId] === undefined || episodeNumber > groupLastEpisode[groupId]) {
3305                groupLastEpisode[groupId] = episodeNumber
3306              }
3307            }
3308          }
3309
3310      });
3311
3312      console.debug('groupEpisode', groupEpisode)
3313      console.debug('groupLastEpisode', groupLastEpisode)
3314      console.debug('programIds', programIds)
3315
3316      if (programIds.length <= 0) {
3317        return;
3318      }
3319
3320      Playback.getPlaybackData(programIds)
3321        .done(function() {
3322
3323          _playbackList = Playback.getList()
3324
3325          // Set progression
3326          for (let i = _playbackList.length - 1; i >= 0; i--) {
3327            if (_playbackList[i] &&  _playbackList[i].puid && _playbackList[i].vuid) {
3328              ProgressBar.setProgressbar(_playbackList[i].puid, _playbackList[i].vuid, null, _playbackList[i].rate);
3329            }
3330          }
3331
3332          //boucle on groupLast
3333          for (const [key, value] of Object.entries(groupVideo)) {
3334            // Keep only data for this group
3335            // get last used
3336            const data = _playbackList.filter(element => value.includes(element.puid))
3337
3338            if (data.length) {
3339              // Find max time value
3340              let maxTime = data[0].time;
3341              var maxId = 0;
3342              for (let i = 0, len = data.length; i < len; i++) {
3343                let currentTime = data[i].time;
3344                if (currentTime > maxTime) {
3345                  maxTime = currentTime
3346                  maxId = i
3347                }
3348              }
3349            }
3350
3351            // if last one is finished, launch next instead
3352            if (maxId in data && data[maxId].rate == 100 && groupEpisode[data[maxId].puid]) {
3353              let nextEpisode = groupEpisode[data[maxId].puid] + 1
3354              const nextPuid = Object.keys(groupEpisode).find(key => groupEpisode[key] === nextEpisode);
3355
3356              let newLast = Playback.getCurrent(nextPuid)
3357              if (newLast === undefined || newLast === null) {
3358                _groupLast[key] = {
3359                  cuid: nextPuid,
3360                  duration: 0,
3361                  vuid: groupVuid[nextPuid]
3362                }
3363              } else {
3364                _groupLast[key] = newLast
3365              }
3366
3367            } else {
3368              _groupLast[key] = data[maxId]
3369            }
3370
3371            if (maxId in data && data[maxId].puid && groupEpisode[data[maxId].puid]) {
3372              const episodeProgramId = data[maxId].puid;
3373              _groupLast[key].episode = getEpisodeNumber($('.video-episode[data-
3373puid="' + episodeProgramId + '"]'));
3374            }
3375
3376            if(_groupLast[key] && _groupLast[key] !== 'undefined' && groupLastEpisode && key in groupLastEpisode) {
3377              _groupLast[key].last_episode = groupLastEpisode[key]
3378            }
3379          }
3380
3381          togglePlaybackLabels();
3382
3383          // Playback of a program will be launched via livewire Program.init
3384          if(! isEpisode() && launchPlayback) {
3385            $('.link-play').first().click();
3386          }
3387
3388          // Serie/season deep link : .link-play now points at the episode to resume, so a
3389          // single click applies the existing "resume, else first episode" resolution.
3390          launchDeepLinkPlayback(true);
3391
3392          // Set progression for alias progress bar
3393          $('.movie-progress[data-linked]').each(function(element){
3394            let id = $( this ).attr('data-group');
3395            if (_groupLast[id]) {
3396              ProgressBar.setProgressbar(0, 0, id, _groupLast[id].rate);
3397            } else {
3398              console.debug('Progress linked : cannot find progress bar for group ' + id);
3399            }
3400          });
3401
3402        })
3403        .fail(function(error) {
3404          console.warn( "cannot get playback data.", error );
3405        });
3406    }
3407
3408    function getEpisodeNumber(element) {
3409      return parseInt(element.data('rank') ?? 1);
3410    }
3411
3412    /**
3413     * limit Reach
3414     *
3415     * @param {string|null} reason The reason for the limit reach (e.g., 'view_count_limit_reached', 'limit_simultaneous_watch_reached')
3416     * @return {[type]} [description]
3417     */
3418    function limitReach(reason = null) {
3419      if (reason !== null) {
3420        _limitReachReason = reason;
3421      } else if (_limitReachReason !== null) {
3422        reason = _limitReachReason;
3423      }
3424      console.log('limitReach', reason);
3425      if (player) {
3426        player.pause();
3427        player.unload()
3428          .then(function(json) { /* traiter le JSON */ })
3429          .catch(function(error) {
3430              OPDebug.log(error);
3431             /* La ligne précédent peut aussi déclencher une
3432                erreur (si console vaut {} par exemple) */
3433           })
3434          .finally(function() {
3435            previous = -1;
3436            _isLimitReach = true;
3437            showLimitReachMessage(reason);
3438          });
3439        } else {
3440            previous = -1;
3441            _isLimitReach = true;
3442            showLimitReachMessage(reason);
3443        }
3444
3445    }
3446
3447
3448    function showLimitReachMessage(reason = null) {
3449
3450      _isLimitReach = true;
3451
3452      $(MODAL_PLAYER).modal('hide');
3453      // $(MODAL_PLAYER).modal('dispose');
3454
3455
3456      $(MODAL_PLAYER).on('shown.bs.modal', function (e) {
3457        $(MODAL_PLAYER).modal('hide');
3458      })
3459      $(MODAL_PLAYER).on('show.bs.modal', function (e) {
3460        $(MODAL_PLAYER).modal('hide');
3461      })
3462      if (reason && reason === 'view_count_limit_reached') {
3463        $(MODAL_LIMIT_VIEW_COUNT).modal('show');
3464      }
3465      else {
3466        $(MODAL_LIMIT).modal('show');
3467      }
3468      return true;
3469    }
3470
3471    /**
3472     * [unloadPlayer description]
3473     * @param  {[type]} errorMessage [description]
3474     * @return void
3475     */
3476    function unloadPlayer(errorMessage = null, errorCode = null) {
3477      player
3478        .unload()
3479        .then(function(json) { /* traiter le JSON */ })
3480        .catch(function(error) {
3481            OPDebug.log(error);
3482           /* La ligne précédent peut aussi déclencher une
3483              erreur (si console vaut {} par exemple) */
3484         })
3485        .finally(function() {
3486          previous = INIT_PREVIOUS;
3487
3488          if (errorMessage) {
3489            $(MODAL_PLAYER).modal('hide');
3490            var strErrorMessage = JSON.stringify(errorMessage);
3491            if (errorCode) {
3492              strErrorMessage += ' (' + errorCode + ')';
3493            }
3494            $(MODAL_ERROR).find('.modal-content-message').text(strErrorMessage); //  + ' | ' +  Array.join(_errorHistory)
3495            $(MODAL_ERROR).modal('show');
3496          }
3497        });
3498    }
3499
3500    /**
3501     * [pushDataLayerVideoEvent description]
3502     *
3503     * @param  {string} type  [description]
3504     * @return void
3505     */
3506    function pushDataLayerVideoEvent(type) {
3507      pushDataLayerEvent({'event': 'video', 'videoType': type, 'videoTitle': _analyticsTitle });
3508    }
3509
3510
3511    /**
3512     * Get the current video rate
3513     *
3514     * @return integer
3515     * @author  Jody JUSTINE <jjustine @vodfactory.com>
3516     */
3517    function getCurrentProgressRate() {
3518      if (player.getSeekableRange().end) {
3519        var retval = Math.ceil((player.getCurrentTime() / player.getSeekableRange().end) * 100);
3520        if (player.getSeekableRange().end - player.getCurrentTime() < 10 ) {
3521          retval = 100
3522        }
3523        return retval >= 100 ? 100 : retval;
3524      }
3525      return 0;
3526    }
3527
3528    /**
3529     * Track video view
3530     *
3531     * @param  {integer} videoId Video id of the wanted video
3532     * @return void
3533     *
3534     * @author Stéphane MONNOT <[email protected]>
3535     */
3536    function trackingVideoView(videoId) {
3537      if (! videoId) {
3538        return;
3539      }
3540
3541      $.ajax({
3542          type: 'POST',
3543          data: {
3544              video_id: videoId
3545          },
3546          url:  "/tracking/video-view",
3547      });
3548
3549      return;
3550    }
3551
3552    function setSettingsPanelUI() {
3553      var subsList = player.subtitles.list();
3554      var $settingsPanelPage = $('#bmpui-id-89');
3555      var $subsParamsBtn = $('#bmpui-id-102');
3556      if ($subsParamsBtn.length > 0) {
3557        if (subsList.length > 0) {
3558          $subsParamsBtn.removeClass('bmpui-hidden');
3559        } else {
3560          $subsParamsBtn.addClass('bmpui-hidden');
3561        }
3562      }
3563      var $settingsPanelPageItems  = $settingsPanelPage.find('.bmpui-ui-settings-panel-item');
3564      var $settingsPanelPageLastItem = $settingsPanelPage.find('.bmpui-ui-settings-panel-item').not('.bmpui-hidden').last();
3565      if ($settingsPanelPageLastItem.length > 0) {
3566        $settingsPanelPageItems.removeClass('bmpui-last');
3567        $settingsPanelPageLastItem.addClass('bmpui-last');
3568      }
3569    }
3570
3571    /**
3572     * @param {int} programId The program id to add to or remove from the favorites
3573     * @param  {boolean} added Added or not to the favorites
3574     * @return void
3575     *
3576     * @author Florian PELLETIER <[email protected]>
3577     */
3578    function setPlayerFavorite(programId, added) {
3579      if (added) {
3580        uiManager.currentUi.events.onFavoriteEnabled.dispatch(uiManager.currentUi);
3581      } else {
3582        uiManager.currentUi.events.onFavoriteDisabled.dispatch(uiManager.currentUi);
3583      }
3584    }
3585
3586
3587    function hasPrerollRatingLevel() {
3588      return _ratingLevelImage !== undefined && _ratingLevelImage !== '';
3589    }
3590
3591    function setPrerollRatingLevel() {
3592      OPDebug.log('Preroll init...' , _ratingLevelImage);
3593      _prerollShown = true;
3594      playerElement.addClass('preroll-rating-level');
3595      player.setPosterImage(_ratingLevelImage, true);
3596    }
3597
3598    function initPrerollRatingLevel() {
3599      const sleep = 5000;
3600
3601      if (hasPrerollRatingLevel()) {
3602        setPrerollRatingLevel();
3603        player.pause();
3604
3605        setTimeout(() => {
3606          OPDebug.log('Preroll init finished, now play');
3607          player.play();
3608          _prerollShown = false;
3609          playerElement.removeClass('preroll-rating-level');
3610        }, sleep);
3611      }
3612    }
3613
3614    // If the page is hidden and player set, pause the video;
3615    // if the page is shown and player set and modal shown, play the video
3616    function handleVisibilityChange() {
3617      OPDebug.log('handleVisibilityChange');
3618
3619      if (_isCasting || ! _pauseOnHide) {
3620        return ;
3621      }
3622
3623      if (player && document[_hidden]) {
3624        player.pause();
3625      } else if (player && ! _isModalHide) {
3626        player.play();
3627      }
3628    }
3629
3630    function setEventFocusBack() {
3631      // Warn if the browser doesn't support addEventListener or the Page Visibility API
3632      if (typeof document.addEventListener === "undefined" || _hidden === undefined) {
3633        console.log("Require a browser, such as Google Chrome, Safari or Firefox, that supports the Page Visibility API.");
3634      } else {
3635        // Handle page visibility change
3636        document.addEventListener(_visibilityChange, handleVisibilityChange, false);
3637      }
3638    }
3639
3640    /**
3641     * Set watch button label
3642     */
3643    function togglePlaybackLabels()
3644    {
3645      let playButtons = $('.link-play');
3646      const programType = playButtons.first().data('program-type');
3647      // If playbacks, we check the last one
3648      if (_playbackList && _playbackList.length > 0) {
3649        _playbackList.forEach(function(playbackData) {
3650          let lastEpisodeFound = false;
3651          let contentId = playbackData.cuid;
3652          let programId = playbackData.puid;
3653          // The first playback with a duration is the last episode playbacked
3654          if (! lastEpisodeFound && playbackData.duration) {
3655            playButtons.attr('data-playback', 1);
3656            let episodeButton = $('.video-episode[data-puid="' + programId + '"]');
3657            // Last episode ?
3658            if (playbackData.episode) {
3659              // First episode from btn watch is not the last one playbacked ?
3660              if (playButtons.first().data('puid') !== programId) {
3661                playButtons.attr('data-cuid', contentId)
3662                  .attr('data-puid', programId)
3663                  .attr('data-vuid', playbackData.vuid)
3664                  .attr('data-title', episodeButton.data('title'));
3665              }
3666              playButtons.find('.btn-playback-label-number').text(' ' + playbackData.episode);
3667              Playback.setPlaybackLabelNumber(playbackData.episode);
3668            }
3669            episodeButton.attr('data-playback', 1);
3670            lastEpisodeFound = true;
3671          }
3672        });
3673      }
3674    }
3675
3676    /**
3677     * At init episodes, set serie play button data (puid, vuid and title) from the first episode
3678     */
3679    function setSeriePlayButton()
3680    {
3681      if (isEpisode()) {
3682        const elemFirstEpisode = $('.video-episode').first();
3683        setSeriePlayButtonFromEpisode(elemFirstEpisode);
3684      }
3685    }
3686
3687    function setSeriePlayButtonFromEpisode(elemEpisode)
3688    {
3689      if (! isEpisode()) {
3690        return;
3691      }
3692
3693      let playButtons = $('.link-play');
3694      playButtons.attr('data-cuid', elemEpisode.data('cuid'))
3695        .attr('data-puid', elemEpisode.data('puid'))
3696        .attr('data-vuid', elemEpisode.data('vuid'))
3697        .attr('data-group', elemEpisode.data('group'))
3698        .attr('data-title', elemEpisode.data('title'));
3699      Playback.setPlaybackLabelNumber(elemEpisode.data('rank'));
3700    }
3701
3702    return {
3703      'init' : init,
3704      'onReady':  onReady,
3705      'onResize': onResize,
3706      'videoAdaptationHandler': videoAdaptationHandler,
3707      'player': player,
3708      'setPlayerFavorite': setPlayerFavorite,
3709      'limitReach': limitReach,
3710      'getCurrentProgressRate': getCurrentProgressRate,
3711      'showLimitReachMessage': showLimitReachMessage,
3712      'pushDataLayerVideoEvent': pushDataLayerVideoEvent,
3713      'togglePlaybackLabels': togglePlaybackLabels,
3714      'initPlayerNextSeasonEpisode': initPlayerNextSeasonEpisode,
3715      'initPlaybackData': initPlaybackData,
3716      'setSeriePlayButton': setSeriePlayButton,
3717    }
3718  })();
3719
3720
3721/**
3722 * OTTO Player Source
3723 *
3724 * @author Jody JUSTINE <[email protected]>
3725 */
3726var OPHelpers = (function () {
3727
3728  var _currentBrowser;
3729  var _currentOsName;
3730
3731  /**
3732   * Return label
3733   *
3734   * @param  {[type]} data [description]
3735   * @return {string}      [description]
3736   *
3737   * @private
3738   *
3739   * @author Jody JUSTINE <[email protected]>
3740   */
3741    var getQualityLabels = function (data) {
3742      try {
3743        if(typeof data === 'undefined' || typeof data.bitrate === 'undefined' || typeof data.width === 'undefined'  || typeof data.height === 'undefined') {
3744          return '';
3745        }
3746
3747        var retval = '';
3748        switch(data.height) {
3749          case 270:
3750            retval = '270p';
3751            break;
3752
3753          case 360:
3754            retval = '360p';
3755            // retval = '360p (' + humanizeBitrate(data.bitrate) + ')';
3756            // if (data.bitrate < 1000000) {
3757            //   retval += ' Low';
3758            // } else if (data.bitrate > 1500000) {
3759            //   retval += ' High';
3760            // }
3761            break;
3762
3763          case 540:
3764            retval = '540p';
3765            break;
3766
3767          case 720:
3768            retval = 'HD 720p';
3769            // retval = 'HD 720p (' + humanizeBitrate(data.bitrate) + ')';
3770            // if (data.bitrate < 5000000) {
3771            //   retval += ' Low';
3772            // } else if (data.bitrate > 5000000) {
3773            //   retval += ' High';
3774            // }
3775            break;
3776
3777          case 1080:
3778            retval = 'HD 1080p';
3779            break;
3780
3781          case 1440:
3782            retval = 'HD 1440p';
3783            break;
3784
3785          case 2160:
3786            retval = '4K 2160p';
3787            break;
3788
3789          default:
3790            retval = data.height + 'p';
3791        }
3792
3793        return retval;
3794      } catch(e) {
3795        console.log(e);
3796        return '';
3797      }
3798  }
3799
3800  /**
3801   * Return audio tag
3802   *
3803   * @param  {string}
3803 iso [description]
3804   * @return {string}      [description]
3805   *
3806   * @private
3807   *
3808   * @author Thomas Técher <[email protected]>
3809   */
3810   var getLangTagName = function(track){
3811    var iso = 'qaa'
3812    if(typeof track.name !== 'undefined'){
3813      iso = track.name
3814    }else if(typeof track.lang !== 'undefined'){
3815      iso = track.lang
3816    }else{
3817      //ignore this case for now
3818    }
3819    if(typeof languages === 'undefined'){
3820      return iso
3821    }
3822    var shortList = languages.filter(function (x){
3823      return x.iso_639_1 === iso || x.iso_639_2 === iso || x.iso_639_3 === iso;
3824    })
3825    if(shortList.length > 0){
3826       return shortList[0].name
3827    }else{
3828       return iso
3829    }
3830  }
3831
3832  /**
3833   * Get url parameter
3834   *
3835   * @return string parameter value
3836   * @author Jody JUSTINE <[email protected]>
3837   */
3838  function getUrlParameter(name)
3839  {
3840    try {
3841      // Escape all regex metacharacters (not just brackets) before interpolating
3842      // `name` into a RegExp, as defense in depth against malformed/ReDoS patterns.
3843      name = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3844      var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
3845      var results = regex.exec(location.search);
3846      return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
3847    } catch (error) {
3848      return ''
3849    }
3850  }
3851
3852  var getBrowser = function (data) {
3853    try {
3854      if ( typeof _currentBrowser !== 'undefined' ) {
3855        return _currentBrowser
3856      }
3857
3858      var parser = new UAParser();
3859      _currentBrowser = parser.getResult();
3860    } catch (e) {
3861      console.log(e)
3862    }
3863    return _currentBrowser;
3864  }
3865
3866  var getBrowserName = function (data) {
3867    _currentBrowserName = ''
3868    try {
3869      _currentBrowserName  = getBrowser().browser.name;
3870    } catch (e) {
3871      console.log(e)
3872    }
3873    return _currentBrowserName;
3874  }
3875
3876  var getBrowserMajor = function (data) {
3877    _currentBrowserName = ''
3878    try {
3879      _currentBrowserName  = getBrowser().browser.major;
3880    } catch (e) {
3881      console.log(e)
3882    }
3883    return _currentBrowserName;
3884  }
3885
3886  var getOsName = function (data) {
3887    _currentOsName = ''
3888    try {
3889      _currentOsName  = getBrowser().os.name;
3890    } catch (e) {
3891      console.log(e)
3892    }
3893    return _currentOsName;
3894  }
3895
3896  var getOsVersion = function (data) {
3897    _currentOsName = ''
3898    try {
3899      _currentOsName  = getBrowser().os.version;
3900    } catch (e) {
3901      console.log(e)
3902    }
3903    return _currentOsName;
3904  }
3905
3906  var getDeviceModel = function (data) {
3907    _currentOsName = ''
3908    try {
3909      _currentOsName  = getBrowser().device.model;
3910    } catch (e) {
3911      console.log(e)
3912    }
3913    return _currentOsName;
3914  }
3915
3916  var humanizeBitrate = function (bitrate) {
3917      var mbit = bitrate / 1000000;
3918      var rounded = mbit < 3 ? Math.round(mbit * 10) / 10 : Math.round(mbit);
3919      return rounded + 'Mbit';
3920  }
3921
3922  var fullScreen = function (item) {
3923    if (!item.fullscreenElement && !item.webkitFullscreenElement && !item.mozFullScreenElement && !item.msFullscreenElement) {
3924      // Kind of painful, but this is how it works for now
3925      if (item.requestFullscreen) {
3926        item.requestFullscreen();
3927      } else if (item.mozRequestFullScreen) {
3928        item.mozRequestFullScreen();
3929      } else if (item.webkitRequestFullscreen) {
3930        item.webkitRequestFullscreen();
3931      } else if (item.msRequestFullscreen) {
3932        item.msRequestFullscreen();
3933      }
3934    }
3935  }
3936
3937  var getSubtitlesPreferences = function () {
3938    if(localStorage.getItem("op_subsPrefs")) {
3939      console.debug('get prefered subtitles language :', localStorage.getItem("op_subsPrefs"))
3940      return localStorage.getItem("op_subsPrefs")
3941    }
3942    return ''
3943  }
3944
3945  var setSubtitlesPreferences = function (langCode) {
3946    console.debug('set prefered subtitles language :', langCode)
3947    localStorage.setItem("op_subsPrefs", langCode)
3948  }
3949
3950  var getAudioPreferences = function () {
3951    if(localStorage.getItem("op_audioPrefs")) {
3952      console.debug('get prefered audio language :', localStorage.getItem("op_audioPrefs"))
3953      return localStorage.getItem("op_audioPrefs")
3954    }
3955    return ''
3956  }
3957
3958  var setAudioPreferences = function (langCode) {
3959    console.debug('set prefered audio language :', langCode)
3960    localStorage.setItem("op_audioPrefs", langCode)
3961  }
3962
3963  return {
3964      'getQualityLabels': getQualityLabels,
3965      'browser':          getBrowser,
3966      'getBrowserName':   getBrowserName,
3967      'getBrowserMajor' : getBrowserMajor,
3968      'getOsName'     :   getOsName,
3969      'getOsVersion'  :   getOsVersion,
3970      'getDeviceModel' :  getDeviceModel,
3971      'getUrlParameter':  getUrlParameter,
3972      'humanizeBitrate':  humanizeBitrate,
3973      'getLangTagName':   getLangTagName,
3974      'fullScreen':       fullScreen,
3975      'getSubtitlesPreferences': getSubtitlesPreferences,
3976      'setSubtitlesPreferences': setSubtitlesPreferences,
3977      'getAudioPreferences': getAudioPreferences,
3978      'setAudioPreferences': setAudioPreferences,
3979  }
3980})();
3981
3982/****************************************************************************
3983 * Copyright (C) 2017, Bitmovin, Inc., All Rights Reserved
3984 *
3985 * This source code and its use and distribution, is subject to the terms
3986 * and conditions of the applicable license agreement.
3987 ****************************************************************************/
3988var bitmovin = window.bitmovin;
3989/**
3990 * Default implementation of the KeyMap to Control the player
3991 */
3992var DefaultPlayerKeymap = /** @class */
3993(function() {
3994    function DefaultPlayerKeymap() {
3995        this.toggle_mute = {
3996            keyBinding: 'm',
3997            callback: function(player) {
3998                if (player.isMuted()) {
3999                    player.unmute();
4000                } else {
4001                    player.mute();
4002                }
4003            }
4004        };
4005        this.enter_fullscreen = {
4006            keyBinding: 'f',
4007            callback: function(player) {
4008                if (player.getViewMode() !== bitmovin.player.ViewMode.Fullscreen) {
4009                    /** IF PLAYER MODAL IS HIDDEN DEACTIVATE FULLSCREEN TOGGLE ACTION **/
4010                    if(typeof MODAL_PLAYER !== 'undefined'
4011                      && document.querySelector(MODAL_PLAYER) !== null
4012                      && document.querySelector(MODAL_PLAYER).className.indexOf('show') === -1
4013                    ) {
4014                      return
4015                    }
4016                    player.setViewMode(bitmovin.player.ViewMode.Fullscreen, {fullscreenElement: document.querySelector(MODAL_PLAYER)});
4017                }
4018            }
4019        };
4020        this.toggle_languages = {
4021            keyBinding: 'c',
4022            callback: function(player) {
4023                $('.bmpui-ui-languagesettingstogglebutton').click();
4024            }
4025        };
4026        this.exit_fullscreen = {
4027            keyBinding: 'f',
4028            callback: function(player) {
4029                if (player.getViewMode() === bitmovin.player.ViewMode.Fullscreen) {
4030                    player.setViewMode(bitmovin.player.ViewMode.Inline);
4031                }
4032            }
4033        };
4034        this.exit_view = {
4035            keyBinding: 'esc',
4036            callback: function(player) {
4037                if (player.getViewMode() === bitmovin.player.ViewMode.Fullscreen) {
4038                    player.setViewMode(bitmovin.player.ViewMode.Inline);
4039                } else {
4040                    $(MODAL_PLAYER).modal('hide');
4041                }
4042            }
4043        };
4044        this.seek_plus10_sec = {
4045            keyBinding: 'ctrl+right / command+right',
4046            callback: function(player) {
4047                player.seek(Math.min(player.getDuration(), player.getCurrentTime() + 30));
4048            }
4049        };
4050        this.seek_minus10_sec = {
4051            keyBinding: 'ctrl+left / command+left',
4052            callback: function(player) {
4053                player.seek(Math.max(0, player.getCurrentTime() - 30));
4054            }
4055        };
4056        this.volume_plus5 = {
4057            keyBinding: 'up',
4058            callback: function(player) {
4059                player.setVolume(Math.min(100, player.getVolume() + 10));
4060            }
4061        };
4062        this.volume_plus10 = {
4063            keyBinding: 'ctrl+up / command+up',
4064            callback: function(player) {
4065                player.setVolume(Math.min(100, player.getVolume() + 30));
4066            }
4067        };
4068        this.volume_minus5 = {
4069            keyBinding: 'down',
4070            callback: function(player) {
4071                player.setVolume(Math.max(0, player.getVolume() - 10));
4072            }
4073        };
4074        this.volume_minus10 = {
4075            keyBinding: 'ctrl+down / command+down',
4076            callback: function(player) {
4077                player.setVolume(Math.max(0, player.getVolume() - 30));
4078            }
4079        };
4080    }
4081    DefaultPlayerKeymap.prototype.getAllBindings = function() {
4082        var retVal = [];
4083        // collect all objects of this keymap
4084        // Do not use a static approach here as everything can be overwritten / extended
4085        for (var attr in this) {
4086            if (typeof this[attr] === 'object') {
4087                retVal.push(this[attr]);
4088            }
4089        }
4090        return retVal;
4091    };
4092    DefaultPlayerKeymap.prototype.getAllBindingsForKey = function(keyRepresentation) {
4093        var retVal = [];
4094        var allBindings = this.getAllBindings();
4095        // split the key command by + and check all parts seperatly so we have the same behavior with ctrl+alt as with alt+ctrl
4096        var allNeededKeys = keyRepresentation.split(KeyboardEventMapper.KeyCommandSeparator);
4097        allBindings.forEach(function(element) {
4098            element.keyBinding.split(KeyboardEventMapper.KeyBindingSeparator).forEach(function(singleBinding) {
4099                var containsAllParts = true;
4100                // make sure that the same amount of keys is needed and then make sure that all keys are contained
4101                var singleBindingParts = singleBinding.split(KeyboardEventMapper.KeyCommandSeparator);
4102                if (allNeededKeys.length === singleBindingParts.length) {
4103                    allNeededKeys.forEach(function(keyCommandPart) {
4104                        if (singleBindingParts.indexOf(keyCommandPart) < 0) {
4105                            containsAllParts = false;
4106                        }
4107                    });
4108                    if (containsAllParts) {
4109                        retVal.push(element);
4110                    }
4111                }
4112            });
4113        });
4114        return retVal;
4115    };
4116    return DefaultPlayerKeymap;
4117}());
4118
4119/**
4120 * Class to control a given player instance via the keyboard
4121 */
4122var PlayerKeyboardControl = /** @class */
4123(function() {
4124    function PlayerKeyboardControl(wrappedPlayer, preventPageScroll, config) {
4125        if (preventPageScroll === void 0) {
4126            preventPageScroll = true;
4127        }
4128        var _this = this;
4129        this.preventScrolling = function(event) {
4130            var keyCode = event.which || event.keyCode;
4131            // prevent scrolling with arrow keys, space, pageUp and pageDown
4132            if (KeyboardEventMapper.isScrollKey(keyCode)) {
4133                // maybe we should check here if we actually have a keybinding for the keyCode and only prevent
4134                // the scrolling if we actually handle the event
4135                if(event.keyCode == 32 && event.target == document.body) {
4136                    e.preventDefault();
4137                }
4138            }
4139        };
4140        this.handleKeyEvent = function(event) {
4141
4142            if (_this.isEnabled && event.target.className != 'search-input') {
4143                var keyStringRepresentation = KeyboardEventMapper.convertKeyboardEventToString(event);
4144                var bindings = _this.keyMap.getAllBindingsForKey(keyStringRepresentation);
4145                bindings.forEach(function(singleBinding) {
4146                    singleBinding.callback(_this.player);
4147                });
4148            }
4149        };
4150        this.player = wrappedPlayer;
4151        this.shouldPreventScrolling = preventPageScroll;
4152        var paramKeyMap = {};
4153        if (config) {
4154            paramKeyMap = config;
4155        }
4156        this.keyMap = PlayerKeyboardControl.mergeConfigWithDefault(paramKeyMap);
4157        // default to enabled
4158        // this also registers the event listeners
4159        this.enable(true);
4160        // destroy this together with the player
4161        this.player.on(bitmovin.player.PlayerEvent.Destroy, function() {
4162            _this.destroy();
4163        });
4164    }
4165    PlayerKeyboardControl.prototype.enable = function(shouldBeEnabled) {
4166        if (shouldBeEnabled === void 0) {
4167            shouldBeEnabled = true;
4168        }
4169        this.isEnabled = shouldBeEnabled;
4170        // depending if we are enabled register or remove the keyListener
4171        // we cannot use the keypress event as that event does not work with modifiers
4172        // only add the keyUp listener as we do not expect users holding buttons to control the player
4173        if (this.isEnabled) {
4174            // in order to stop the browser from scrolling we have to add an additional onKeyDown listener
4175            // because the browser would scroll on that one already
4176            if (this.shouldPreventScrolling) {
4177                document.addEventListener('keydown', this.preventScrolling, false);
4178            }
4179            document.addEventListener('keyup', this.handleKeyEvent, false);
4180        } else {
4181            // document.addEventListener('keypress', this.handleKeyEvent, false);
4182            document.removeEventListener('keydown', this.preventScrolling, false);
4183            document.removeEventListener('keyup', this.handleKeyEvent, false);
4184        }
4185    };
4186    PlayerKeyboardControl.prototype.disable = function(shouldBeDisabled) {
4187        if (shouldBeDisabled === void 0) {
4188            shouldBeDisabled = true;
4189        }
4190        this.enable(!shouldBeDisabled);
4191    };
4192    /**
4193     * Use this method to prevent the browser from scrolling via keyboard
4194     * @param preventScrolling true if keyboard scrolling should be prevented, false if nots
4195     */
4196    PlayerKeyboardControl.prototype.setPreventScrolling = function(preventScrolling) {
4197        this.shouldPreventScrolling = preventScrolling;
4198        // set up or remove the listener if necessary
4199        if (this.isEnabled) {
4200            if (preventScrolling) {
4201                document.addEventListener('keydown', this.preventScrolling, false);
4202            } else {
4203                document.removeEventListener('keydown', this.preventScrolling, false);
4204            }
4205        }
4206    };
4207    PlayerKeyboardControl.prototype.destroy = function() {
4208        // removes the listener
4209        this.disable(true);
4210    };
4211    PlayerKeyboardControl.mergeConfigWithDefault = function(paramKeyMap) {
4212        var retVal = new DefaultPlayerKeymap();
4213        // allow overwrites to the default player keymap as well as new listeners
4214        for (var attr in paramKeyMap) {
4215            if (attr && paramKeyMap[attr]) {
4216                var toCheck = paramKeyMap[attr];
4217                // avoid wrong configs and check for elements being real keyListeners
4218                if (toCheck['keyBinding'] && toCheck['callback']) {
4219                    retVal[attr] = paramKeyMap[attr];
4220                } else {
4221                    console.log('Invalid Key Listener at params[' + attr + ']');
4222                }
4223            }
4224        }
4225        return retVal;
4226    };
4227    return PlayerKeyboardControl;
4228}());
4229
4230/**
4231 * Class to handle mappings from KeyboardEvent.keyCode to a string representation
4232 */
4233var KeyboardEventMapper = /** @class */
4234(function() {
4235    function KeyboardEventMapper() {}
4236    /**
4237     * Converts a Keyboard Event to something like shit+alt+g depending on the character code of the event and the modifiers
4238     * @param event the event to be converted into a string
4239     * @returns {string} the representation of the all keys which were pressed
4240     */
4241    KeyboardEventMapper.convertKeyboardEventToString = function(event) {
4242        var retVal = '';
4243        var needsConcat = false;
4244        if (event.shiftKey) {
4245            retVal += 'shift';
4246            needsConcat = true;
4247        }
4248        if (event.altKey) {
4249            if (needsConcat) {
4250                retVal += KeyboardEventMapper.KeyCommandSeparator;
4251            } else {
4252                needsConcat = true;
4253            }
4254            retVal += 'alt';
4255        }
4256        if (event.ctrlKey || event.metaKey) {
4257            if (needsConcat) {
4258                retVal += KeyboardEventMapper.KeyCommandSeparator;
4259            } else {
4260                needsConcat = true;
4261            }
4262            retVal += 'ctrl';
4263        }
4264        var convertedCode = KeyboardEventMapper.convertKeyCodeToString(event);
4265        if (convertedCode) {
4266            if (needsConcat) {
4267                retVal += KeyboardEventMapper.KeyCommandSeparator;
4268            }
4269            retVal += convertedCode;
4270        } else {
4271            console.log('No conversion for the code: ' + event.keyCode);
4272        }
4273        return retVal;
4274    };
4275    /**
4276     * Tries to convert a given keyCode to a string representation of the key
4277     * @param event the event which contains the keyCode
4278     * @returns {string} the string representation of the keyCode (eg.: 'left', 'esc', 'space', 'a', '1' ...)
4279     */
4280    KeyboardEventMapper.convertKeyCodeToString = function(event) {
4281        var code = event.which || event.keyCode;
4282        var retVal;
4283        if (KeyboardEventMapper.isModifierKey(code)) {
4284            retVal = KeyboardEventMapper.ModifyerKeys[code];
4285        } else if (KeyboardEventMapper.isControlKey(code)) {
4286            retVal = KeyboardEventMapper.ControlKeys[code];
4287        } else if (KeyboardEventMapper.isNumblockKey(code)) {
4288            retVal = KeyboardEventMapper.NumblockKeys[code];
4289        } else if (KeyboardEventMapper.isFKey(code)) {
4290            retVal = KeyboardEventMapper.F_Keys[code];
4291        } else {
4292            // try and convert a unicode character
4293            retVal = String.fromCharCode(code).toLowerCase();
4294        }
4295        return retVal;
4296    };
4297    KeyboardEventMapper.isModifierKey = function(keyCode) {
4298        return KeyboardEventMapper.ModifyerKeys.hasOwnProperty('' + keyCode);
4299    };
4300    KeyboardEventMapper.isControlKey = function(keyCode) {
4301        return KeyboardEventMapper.ControlKeys.hasOwnProperty('' + keyCode);
4302    };
4303    KeyboardEventMapper.isNumblockKey = function(keyCode) {
4304        return KeyboardEventMapper.NumblockKeys.hasOwnProperty('' + keyCode);
4305    };
4306    KeyboardEventMapper.isFKey = function(keyCode) {
4307        return KeyboardEventMapper.F_Keys.hasOwnProperty('' + keyCode);
4308    };
4309    KeyboardEventMapper.isScrollKey = function(keyCode) {
4310        return KeyboardEventMapper.ScrollingKeys.hasOwnProperty('' + keyCode);
4311    };
4312    KeyboardEventMapper.KeyBindingSeparator = ' / ';
4313    KeyboardEventMapper.KeyCommandSeparator = '+';
4314    /**
4315     * keys which will represented as a modifier
4316     */
4317    KeyboardEventMapper.ModifyerKeys = {
4318        16: 'shift',
4319        17: 'ctrl',
4320        18: 'alt',
4321        20: 'capslock',
4322        91: 'meta',
4323        93: 'meta',
4324        224: 'meta'
4325    };
4326    /**
4327     * Special keys on the keyboard which are not modifiers
4328     */
4329    KeyboardEventMapper.ControlKeys = {
4330        8: 'backspace',
4331        9: 'tab',
4332        13: 'enter',
4333        19: 'pause',
4334        27: 'esc',
4335        32: 'space',
4336        33: 'pageup',
4337        34: 'pagedown',
4338        35: 'end',
4339        36: 'home',
4340        37: 'left',
4341        38: 'up',
4342        39: 'right',
4343        40: 'down',
4344        44: 'print',
4345        45: 'ins',
4346        46: 'del',
4347        145: 'scrolllock',
4348        186: ';',
4349        187: '=',
4350        188: ',',
4351        189: '-',
4352        190: '.',
4353        191: '/',
4354        192: '`',
4355        219: '[',
4356        220: '\\',
4357        221: ']',
4358        222: '\''
4359    };
4360    /**
4361     * Keys wich normally move the page
4362     */
4363    KeyboardEventMapper.ScrollingKeys = {
4364        32: 'space',
4365        33: 'pageup',
4366        34: 'pagedown',
4367        35: 'end',
4368        36: 'home',
4369        37: 'left',
4370        38: 'up',
4371        39: 'right',
4372        40: 'down',
4373    };
4374    /**
4375     * All number on the numblock an the keys surrounding it
4376     */
4377    KeyboardEventMapper.NumblockKeys = {
4378        96: '0',
4379        97: '1',
4380        98: '2',
4381        99: '3',
4382        100: '4',
4383        101: '5',
4384        102: '6',
4385        103: '7',
4386        104: '8',
4387        105: '9',
4388        106: '*',
4389        107: '+',
4390        109: '-',
4391        110: '.',
4392        111: '/',
4393        144: 'numlock'
4394    };
4395    /**
4396     * F1 - F19
4397     */
4398    KeyboardEventMapper.F_Keys = {
4399        112: 'F1',
4400        113: 'F2',
4401        114: 'F3',
4402        115: 'F4',
4403        116: 'F5',
4404        117: 'F6',
4405        118: 'F7',
4406        119: 'F8',
4407        120: 'F9',
4408        121: 'F10',
4409        122: 'F11',
4410        123: 'F12',
4411        124: 'F13',
4412        125: 'F14',
4413        126: 'F15',
4414        127: 'F16',
4415        128: 'F17',
4416        129: 'F18',
4417        130: 'F19',
4418    };
4419    return KeyboardEventMapper;
4420}());
4421
4422
4423var OPLatency = (function () {
4424
4425  var _last = 5;
4426  var _history = [];
4427
4428  function save(latency) {
4429    _history.unshift(latency.toFixed(3));
4430    _history = _history.slice(0, 50);
4431  }
4432
4433  function getMaxFromLast(number) {
4434    var sliceArr = _history.slice(0, number);
4435    return Math.max.apply(Math, sliceArr);
4436  }
4437
4438  return {
4439    'save' : save,
4440    'getMaxFromLast' : getMaxFromLast,
4441  }
4442})();
4443
4444
4445function arr_diff (a1, a2) {
4446
4447    var a = [], diff = [];
4448
4449    for (var i = 0; i < a1.length; i++) {
4450        a[a1[i]] = true;
4451    }
4452
4453    for (var i = 0; i < a2.length; i++) {
4454        if (a[a2[i]]) {
4455            delete a[a2[i]];
4456        } else {
4457            a[a2[i]] = true;
4458        }
4459    }
4460
4461    for (var k in a) {
4462        diff.push(k);
4463    }
4464
4465    return diff;
4466}
4467
4468
4469function bytesToSize(bytes) {
4470    var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
4471    if (bytes == 0) return 'n/a';
4472    var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
4473    if (i == 0) return bytes + ' ' + sizes[i];
4474    return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
4475}
4476
4477
4478const Playback = (function () {
4479  // video unique identifier
4480  var _videoId = 0;
4481  // content unique identifier
4482  var _cuid = 0;
4483  // Playback list
4484  var _list = [];
4485
4486  var _groupId;
4487
4488  // Period
4489  var _period = 240;
4490
4491  var _isPlaybackSaved = false;
4492
4493  var _playbackLastSaved = 0;
4494  var _playbackLastSaving = false;
4495  var _playbackList = null;
4496
4497  /**
4498   * Is playback allowed
4499   *
4500   * @return {Boolean}
4501   */
4502  function isAllowed() {
4503    if (!_videoId || !_puid) {
4504      return false;
4505    }
4506    return true;
4507  }
4508
4509  function enable(element) {
4510
4511    _playbackLastSaved = 0;
4512
4513    _isPlaybackSaved = element.attr('data-pbsv');
4514    if (isNaN(_isPlaybackSaved)) {
4515      _isPlaybackSaved = 0;
4516    } else {
4517      _isPlaybackSaved = 1;
4518    }
4519
4520    _cuid = element.attr('data-cuid');
4521    _puid = element.attr('data-puid');
4522    if (isNaN(_puid)) {
4523      _cuid = 0;
4524      _puid = 0;
4525    }
4526
4527    _isPlaybackSaved = _videoId && _puid && _isPlaybackSaved == 0 ? 0 : 1
4528
4529  }
4530
4531  function setCuid(value) {
4532    _cuid = value;
4533  }
4534
4535  function getCuid(value) {
4536    return _cuid;
4537  }
4538
4539  function setVideoId(value) {
4540    _videoId = value;
4541  }
4542
4543  function getVideoId(value) {
4544    return _videoId;
4545  }
4546
4547  function setGroupId(value) {
4548    _groupId = value;
4549  }
4550
4551  function showPlaybacklabel(element, episode = 0) {
4552    try {
4553      element.attr('data-playback', 1);
4554      if (episode && !isNaN(episode) && episode !== undefined) {
4555        element.find( ".btn-playback-label-number" ).text(' ' + episode)
4556      }
4557    } catch (error) {
4558      console.warn('hidePlaybacklabel', error)
4559    }
4560  }
4561
4562  function hidePlaybacklabel(element) {
4563    try {
4564      element.attr('data-playback', 0);
4565    } catch (error) {
4566      console.warn('hidePlaybacklabel', error)
4567    }
4568  }
4569
4570  function setPlaybackLabelNumber(episode = 0) {
4571    if (episode && !isNaN(episode) && episode !== undefined) {
4572      $('.link-play .btn-playback-label-number').text(' ' + episode);
4573    }
4574  }
4575
4576  /**
4577   * [setList description]
4578   * @param {[type]} list [description]
4579   * @return {void}
4580   */
4581  function setList(list) {
4582    _playbackList = list;
4583  }
4584
4585  function getList(list) {
4586    return _playbackList;
4587  }
4588
4589  function getPlaybackData(programListId) {
4590    return $.ajax({
4591      type: 'POST',
4592      url: '/api/playbacks/list',
4593      data: {'program_id': programListId},
4594      dataType: "json",
4595      beforeSend: function (xhr) {
4596        xhr.overrideMimeType("text/plain; charset=x-user-defined");
4597      },
4598      timeout: 15000,
4599    })
4600      .done(function (apiData) {
4601        Playback.setList(apiData.results);
4602      })
4603      .fail(function () {
4604        console.debug("Error on list retrieved.");
4605      })
4606  }
4607
4608  /**
4609   * Save Playback Data
4610   *
4611   * @param  {integer} time from the start of the video in seconds
4612   * @param  {boolean} force save
4613   * @return void
4614   * @author Jody JUSTINE <[email protected]>
4615   */
4616  function saveCurrentData(player, force = false) {
4617    console.log('saveCurrentData');
4618    
4619    const time = player.getCurrentTime();
4620    const subtitles = player.subtitles ? player.subtitles.list().find(element => element.enabled === true) : []
4621    let subtitle = subtitles && subtitles.lang ? subtitles.lang : null;
4622    let audio = player.getAudio() ? ( player.getAudio().lang ?? null) : null
4623    if (audio === null || audio === '') {
4624      audio = 'fra';
4625    }
4626
4627    try {
4628      if (Playback.isAllowed() === false) {
4629        console.debug('Playback Not allowed.');
4630        return;
4631      }
4632
4633      if (_playbackLastSaving) {
4634        return;
4635      }
4636
4637      if (force === false && ((time - _playbackLastSaved) < _period)) {
4638        return;
4639      }
4640
4641      if(!_isPlaybackSaved || !_puid || !_videoId) {
4642        return;
4643      }
4644
4645      var token = $.ajaxSettings.headers["X-CSRF-TOKEN"];
4646      delete $.ajaxSettings.headers["X-CSRF-TOKEN"];
4647
4648      $.ajax({
4649        type: 'POST',
4650        url:  '/api/playbacks/',
4651        dataType: "json",
4652        data: {
4653          'program_id' : _puid,
4654          'video_id': _videoId,
4655          'duration': Math.ceil(time),
4656          'audio': audio,
4657          'subtitle': subtitle
4658        },
4659        beforeSend: function( xhr ) {
4660          _playbackLastSaving = true;
4661          xhr.overrideMimeType("text/plain; charset=x-user-defined");
4662        }
4663      })
4664      // It never go here if the API fails
4665      .done(function( data) {
4666
4667        if (data && data.status === 'success' && data.results && data.results.allowed === true) {
4668
4669          _playbackLastSaved = time;
4670          ProgressBar.setProgressbar(_puid, _videoId, _groupId, ottoPlayer.getCurrentProgressRate() );
4671
4672          if (_groupId && typeof _groupLast !== 'undefined') {
4673            _groupLast[_groupId] = {
4674              cuid: parseInt(_cuid, 10),
4675              puid: parseInt(_puid, 10),
4676              duration: parseInt(player.getCurrentTime(), 10),
4677              episode: currentEpisode,
4678              rate: ottoPlayer.getCurrentProgressRate(),
4679              time: Date.now(),
4680              vuid: parseInt(_videoId, 10)
4681            }
4682          }
4683
4684        } else {
4685          // Extract reason from API response if available
4686          var reason = null;
4687          if (data && data.results && data.results.reason) {
4688            reason = data.results.reason;
4689          }
4690          ottoPlayer.limitReach(reason);
4691        }
4692
4693      })
4694      // It should go here if the API fails
4695      .fail(function() {
4696        console.debug( "Playback not saved." );
4697      })
4698      .always(function() {
4699        _playbackLastSaving = false
4700      })
4701      return;
4702
4703    } catch(error) {
4704      console.error('Error on saving playback');
4705      console.error(error);
4706    }
4707
4708  }
4709
4710  function getCurrent(programId = null) {
4711    try {
4712      if (! _playbackList) {
4713        return null;
4714      }
4715
4716      return _playbackList.find(element => element.puid == (programId ?? _puid))
4717    } catch (error) {
4718      console.warn('getPlaybackByPuid', error)
4719    }
4720    return null;
4721  }
4722
4723  function shouldSaved(element) {
4724
4725    _isPlaybackSaved = element.attr('data-pbsv');
4726    if (isNaN(_isPlaybackSaved)) {
4727      _isPlaybackSaved = 0;
4728    } else {
4729      _isPlaybackSaved = 1;
4730    }
4731    _isPlaybackSaved = _vuid && _puid && _isPlaybackSaved === 0 ? 0 : 1
4732
4733    return _isPlaybackSaved;
4734  }
4735
4736
4737  return {
4738    // Allowed
4739    'isAllowed': isAllowed,
4740    // Enable
4741    'enable': enable,
4742    //
4743    'setList': setList,
4744    'getList': getList,
4745    // Cuid
4746    'setCuid': setCuid,
4747    'getCuid': getCuid,
4748    // Video Id
4749    'setVideoId': setVideoId,
4750    'getVideoId': getVideoId,
4751    //
4752    'setGroupId': setGroupId,
4753    //
4754    'showPlaybacklabel': showPlaybacklabel,
4755    'hidePlaybacklabel': hidePlaybacklabel,
4756    'setPlaybackLabelNumber': setPlaybackLabelNumber,
4757    // Get playback data
4758    'getPlaybackData': getPlaybackData,
4759
4760    'saveCurrentData': saveCurrentData,
4761
4762    'getCurrent': getCurrent,
4763  }
4764})();
4765
4766
4767const PlayerConfig = (function () {
4768    // Platform unique identifier
4769    var _audioLanguage = false;
4770    var _subLanguage = false;
4771    var _volume = 0;
4772    var _webSubSettings = '';
4773    var _appSubSettings = '';
4774    var _type = '';
4775    var _groupId;
4776
4777    // Period
4778    var _period = 30;
4779    var _isPlayerConfigSaved = false;
4780
4781    var _playerConfigLastSaved = 0;
4782    var _playerConfigLastSaving = false;
4783    var _max_save_try = 5;
4784    var _current_save_try = 0;
4785
4786    /**
4787     * Is player config allowed
4788     *
4789     * @return {boolean}
4790     */
4791    function isAllowed() {
4792        if ( _audioLanguage === false || _subLanguage === false) {
4793            return false;
4794        }
4795        return true;
4796    }
4797
4798    /**
4799     * Set the config
4800     *
4801     * @param {object} config
4802     * @return {void}
4803     */
4804    function setConfig(config) {
4805      if (config && 'id' in config) {
4806        _id = config.id;
4807        _subLanguage = config.subtitle_language;
4808        _audioLanguage = config.audio_language;
4809        _volume = config.volume;
4810        _webSubSettings = config.web_subtitle_settings;
4811        _appSubSettings = config.app_subtitle_settings;
4812        _type = config.type;
4813
4814        OPHelpers.setAudioPreferences(_audioLanguage);
4815        OPHelpers.setSubtitlesPreferences(_subLanguage);
4816      }
4817    }
4818
4819    function setPlayerConfigData(data) {
4820      PlayerConfig.setConfig(data);
4821    }
4822
4823    function canSavePlayer(player) {
4824      _current_save_try++;
4825
4826      if (_current_save_try > _max_save_try) {
4827        _current_save_try = 0;
4828        return;
4829      } else if (! _playerConfigLastSaving) {
4830        PlayerConfig.saveCurrentData(player);
4831      } else {
4832          setTimeout(function() {
4833            PlayerConfig.canSavePlayer(player);
4834          }, 5000);
4835      }
4836    }
4837
4838    /**
4839     * Save Player Config Data
4840     *
4841     * @param {object} player
4842     * @param {boolean} force
4843     * @return {void}
4844     * @author Florian PELLETIER <[email protected]>
4845     * @deprecated seems unused
4846     */
4847    function saveCurrentData(player, force = false) {
4848        const subtitles = player.subtitles ? player.subtitles.list().find(element => element.enabled === true) : []
4849        let subtitle = subtitles && subtitles.lang ? subtitles.lang : '';
4850        let audio = player.getAudio() ? ( player.getAudio().lang ?? '') : '';
4851        _subLanguage = subtitle;
4852        _audioLanguage = audio;
4853
4854        try {
4855            if (PlayerConfig.isAllowed() === false) {
4856                console.debug('PlayerConfig Not allowed.');
4857                return;
4858            }
4859            if (_playerConfigLastSaving) {
4860              PlayerConfig.canSavePlayer(player);
4861              return;
4862            }
4863
4864            var token = $.ajaxSettings.headers["X-CSRF-TOKEN"];
4865            delete $.ajaxSettings.headers["X-CSRF-TOKEN"];
4866
4867            $.ajax({
4868                type: 'PUT',
4869                url : '/api/player-configs',
4870                dataType: "json",
4871                data: {
4872                  'audio': audio,
4873                  'subtitle': subtitle,
4874                },
4875                beforeSend: function( xhr ) {
4876                    _playerConfigLastSaving = true
4877                    xhr.overrideMimeType( "text/plain; charset=x-user-defined" );
4878                }
4879            })
4880            .fail(function() {
4881                console.debug( "PlayerConfig not saved." );
4882            })
4883            .always(function() {
4884                _playerConfigLastSaving = false
4885            })
4886            return;
4887        } catch (error) {
4888            console.error('Error on saving player configs');
4889            console.error(error);
4890        }
4891    }
4892
4893    /**
4894     * Set user id
4895     *
4896     * @param {int} value
4897     * @return {void}
4898     */
4899    function setUserId(value) {
4900        _userId = value;
4901    }
4902
4903    /**
4904     * Get user id
4905     *
4906     * @return {int}
4907     */
4908    function getUserId() {
4909        return _userId;
4910    }
4911
4912    /**
4913     * Set audio preference
4914     *
4915     * @param {string} value
4916     * @return {void}
4917     */
4918    function setAudio(value) {
4919        _audioLanguage = value;
4920    }
4921
4922    /**
4923     * Get audio
4924     *
4925     * @return {string}
4926     */
4927    function getAudio() {
4928        return _audioLanguage;
4929    }
4930
4931    /**
4932     * Set subtitle preference
4933     *
4934     * @param {string} value
4935     * @return {void}
4936     */
4937    function setSubtitles(value) {
4938        _subLanguage = value;
4939    }
4940
4941    /**
4942     * Get subtitles
4943     *
4944     * @return {string}
4945     */
4946    function getSubtitles() {
4947        return _subLanguage;
4948    }
4949
4950    /**
4951     * Set group id
4952     *
4953     * @param {int} value
4954     * @return {void}
4955     */
4956    function setGroupId(value) {
4957        _groupId = value;
4958    }
4959
4960    return {
4961        'isAllowed': isAllowed,
4962        'setConfig': setConfig,
4963        'setUserId': setUserId,
4964        'getUserId': getUserId,
4965        'setAudio': setAudio,
4966        'getAudio': getAudio,
4967        'setSubtitles': setSubtitles,
4968        'getSubtitles': getSubtitles,
4969        'setGroupId': setGroupId,
4970        'setPlayerConfigData': setPlayerConfigData,
4971        'saveCurrentData': saveCurrentData,
4972        'canSavePlayer': canSavePlayer
4973    }
4974})();
4975
4976
4977class ProgressBar {
4978
4979
4980
4981  /**
4982   * Set Progressbar size
4983   *
4984   * @param {int} puid    program id
4985   * @param {int} vuid    video id
4986   * @param {int} groupid [description]
4987   * @param {int} rate    [description]
4988   *
4989   * @return {void}
4990   *
4991   * @author  Jody JUSTINE <[email protected]>
4992   */
4993  static setProgressbar(puid, vuid, groupid, rate) {
4994    if (puid) {
4995      $('.movie-progress[data-puid=' + puid + ']')
4996        .each(function(element) {
4997          $( this ).addClass('movie-progress-show');
4998        });
4999
5000      $('.movie-progress[data-puid=' + puid + '] > span')
5001        .each(function(element){
5002          $( this ).removeClass().addClass('movie-progress-width-' + rate);
5003        });
5004    }
5005
5006    if (groupid) {
5007      $('.movie-progress[data-group=' + groupid + '][data-linked]')
5008      .each(function(element) {
5009        $( this ).addClass('movie-progress-show');
5010      });
5011
5012      $('.movie-progress[data-group=' + groupid + '][data-linked] > span')
5013        .each(function(element){
5014          $( this ).removeClass().addClass('movie-progress-width-' + rate);
5015        });
5016    }
5017
5018
5019  }
5020
5021}

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.