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 + ': ' + 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.