1if ( typeof fcaPcOptions === 'object' && fcaPcOptions.video_enabled ) { 2 3 class EventEmitter { 4 5 constructor() { 6 this.events = {} 7 } 8 9 on(event, listener) { 10 if (typeof this.events[event] !== 'object') { 11 this.events[event] = [] 12 } 13 this.events[event].push(listener) 14 return () => this.removeListener(event, listener) 15 } 16 17 removeListener(event, listener) { 18 if (typeof this.events[event] === 'object') { 19 const idx = this.events[event].indexOf(listener) 20 if (idx > -1) { 21 this.events[event].splice(idx, 1) 22 } 23 } 24 } 25 26 emit(event, ...args) { 27 if (typeof this.events[event] === 'object') { 28 this.events[event].forEach(listener => listener.apply(this, args)) 29 } 30 } 31 32 once(event, listener) { 33 const remove = this.on(event, (...args) => { 34 remove() 35 listener.apply(this, args) 36 }) 37 } 38 } 39 40 class FCA_PC_Video extends EventEmitter { 41 42 constructor(selector) { 43 super() 44 45 this.video_ready = false 46 47 if (typeof selector === undefined) { 48 return 49 } 50 51 this.element = selector 52 this.init() 53 54 } 55 56 init() { 57 58 this.setup_events() 59 this.type = this.video_type() 60 this.load_api() 61 } 62 63 load_api() { 64 switch (this.type) { 65 case 'YOUTUBE': 66 this.api_youtube_load() 67 break 68 case 'WISTIA': 69 this.api_wistia_load() 70 break 71 case 'VIMEO': 72 this.api_vimeo_load() 73 break 74 } 75 } 76 77 setup_events() { 78 this.on( 'api_youtube_loaded', this.api_youtube_setup ) 79 this.on( 'api_vimeo_loaded', this.api_vimeo_setup ) 80 this.on( 'api_wistia_loaded', this.api_wistia_setup ) 81 } 82 83 video_type() { 84 let src = this.element.attr('src') 85 86 if (src === '') { 87 this.revoke( 'pixel cat video: video link not found.' ) 88 return false 89 } 90 91 if (src.indexOf('youtube.com') > -1) return 'YOUTUBE' 92 if (src.indexOf('vimeo.com') > -1) return 'VIMEO' 93 if (src.indexOf('fast.wistia.com') > -1) return 'WISTIA' 94 } 95 96 revoke( message ) { 97 if ( fcaPcOptions.debug ) { 98 console.log( message ) 99 console.log( 'pixel cat video: revoked ') 100 } 101 // Do some code that revoke video JS here. 102 } 103 104 //VIMEO 105 106 api_vimeo_load() { 107 jQuery.getScript( 'https://player.vimeo.com/api/player.js', () => { 108 this.emit( 'api_vimeo_loaded' ) 109 } ) 110 } 111 112 api_vimeo_setup() { 113 let player_dom = this.element[0] 114 try { 115 this.api = new Vimeo.Player(player_dom) 116 this.api.ready().then( () => { 117 this.api_vimeo_ready() 118 } ) 119 } catch ( e ) { 120 if ( fcaPcOptions.debug) 121 console.log( e.message ) 122 return 123 } 124 } 125 126 api_vimeo_ready() { 127 if (fcaPcOptions.debug) { 128 console.log( 'Pixel Cat video: Vimeo video ready' ) 129 } 130 131 this.video_ready = true 132 133 this.api.getVideoId().then( id => { 134 this.video_id = id 135 } ) 136 137 this.api.getVideoTitle().then( title => { 138 this.title = title 139 } ) 140 141 this.api.getVideoUrl().then( url => { 142 this.url = url 143 } ) 144 145 this.api.getDuration().then( duration => { 146 this.duration = duration 147 } ) 148 149 this.events_triggered = {} 150 151 this.api_vimeo_state_change() 152 } 153 154 api_vimeo_state_change() { 155 this.api.on( 'play', event => { 156 this.trigger_event( 'VideoPlay' ) 157 this.timer = setInterval( this.track_progress.bind(this), 1000 ) 158 } ) 159 160 this.api.on( 'pause', event => { 161 this.trigger_event( 'VideoPause', { percentage_watched: this.percentage_watched() + '%' } ) 162 clearInterval( this.timer ) 163 } ) 164 165 this.api.on( 'ended', event => { 166 this.trigger_event( 'VideoWatchToEnd' ) 167 clearInterval( this.timer ) 168 } ) 169 } 170 171 //WISTIA 172 173 api_wistia_load() { 174 jQuery.getScript( '//fast.wistia.net/assets/external/E-v1.js', () => { 175 this.emit( 'api_wistia_loaded' ) 176 } ) 177 } 178 179 api_wistia_setup() { 180 181 let player_dom = this.element 182 183 let video_src = player_dom.attr('src') 184 185 if (typeof video_src === 'undefined') { 186 return 187 } 188 189 //GET VIDEO ID FROM URL 190 191 const pattern = /(https:\/\/fast\.wistia\.com\/embed\/medias)\/(.*)\.jsonp/ 192 193 let match = video_src.match(pattern) 194 195 if (match === null) { 196 197 if ( fcaPcOptions.debug ) { 198 console.log('no video id match') 199 } 200 201 return 202 } 203 204 this.url = match[1] 205 206 let video_id = match[2] 207 208 if ( typeof video_id === 'undefined' ) { 209 if ( fcaPcOptions.debug ) { 210 console.log ( 'pixel cat video: wistia video_id not found.' ) 211 } 212 return 213 } 214 215 window._wq = window._wq || [] 216 217 this.api = window._wq 218 219 this.api.push( { 220 id: video_id, 221 onReady: this.api_wistia_ready.bind(this) 222 } ) 223 } 224 225 api_wistia_ready( video ) { 226 227 if ( fcaPcOptions.debug ) { 228 console.log ( 'pixel cat video: wistia video ready.' ) 229 } 230 231 this.video_ready = true 232 this.api = video 233 234 this.video_id = video.data.media.hashedId 235 this.title = video.data.media.name !== undefined ? video.data.media.name : 'Untitled' 236 this.url = this.url + '/' + this.video_id 237 this.events_triggered = {} 238 this.api_wistia_state_change.bind(this)() 239 } 240 241 api_wistia_state_change() { 242 this.api.bind( 'play', event => { 243 this.trigger_event( 'VideoPlay' ) 244 } ) 245 246 this.api.bind( 'pause', event => { 247 this.trigger_event( 'VideoPause', { percentage_watched: this.percentage_watched() + '%' } ) 248 } ) 249 250 this.api.bind( 'percentwatchedchanged', ( percent, lastPercent ) => { 251 this.track_progress() 252 } ) 253 254 this.api.bind( 'end', event => { 255 this.trigger_event( 'VideoWatchToEnd' ) 256 } ) 257 } 258 259 //YOUTUBE 260 261 api_youtube_load() { 262 this.load_async_script( 'https://www.youtube.com/iframe_api' ) 263 } 264 265 api_youtube_setup() { 266 let player_dom = this.element[0] 267 this.api = new YT.Player(player_dom, { 268 events: { 269 'onReady': this.api_youtube_ready.bind(this), 270 'onStateChange': this.api_youtube_state_change.bind(this) 271 } 272 }) 273 } 274 275 api_youtube_ready( event ) { 276 277 if ( fcaPcOptions.debug ) { 278 console.log( 'pixel cat video: youtube video ready') 279 } 280 281 // @todo: we will need to build this one in future to make sure the video is ready to use the object 282 this.video_ready = true 283 this.data = this.api.getVideoData() 284 this.video_id = this.data.video_id 285 this.title = this.data.title 286 this.url = this.api.getVideoUrl() 287 288 this.events_triggered = {} 289 } 290 291 api_youtube_state_change( event ) { 292 if ( fcaPcOptions.debug ) { 293 console.log( event ) 294 } 295 296 switch ( event.data ) { 297 case 0: 298 // end video 299 this.trigger_event( 'VideoWatchToEnd' ) 300 clearInterval( this.timer ) 301 break 302 303 case 1: 304 // play video 305 this.trigger_event( 'VideoPlay' ) 306 // Since we check progress using range: ( percent >= 10 && percent < 25 ) 307 // So we do not need to set interval too fast. 1 - 2 seconds should be fine 308 this.timer = setInterval( this.track_progress.bind(this), 1000 ) 309 break 310 311 case 2: 312 // pause video 313 this.trigger_event( 'VideoPause', { percentage_watched: this.percentage_watched() + '%' } ) 314 clearInterval( this.timer ) 315 break 316 } 317 } 318 319 video_youtube_end() { 320 if ( fcaPcOptions.debug ) { 321 console.log( 'end' ) 322 } 323 } 324 325 load_async_script( url ) { 326 let new_script = document.createElement('script') 327 let first_script = document.getElementsByTagName('script')[0] 328 329 new_script.src = url 330 331 first_script.parentNode.insertBefore(new_script, first_script) 332 333 if ( fcaPcOptions.debug ) { 334 console.log('pixel cat video: ' + url + ' has been loaded.') 335 } 336 } 337 338 track_progress() { 339 switch( this.video_type() ) { 340 case 'YOUTUBE':
341 case 'VIMEO': 342 case 'WISTIA': 343 this.video_track_progress() 344 break 345 346 // Other cases 347 } 348 } 349 350 video_track_progress() { 351 let percent = this.percentage_watched() 352 353 if ( fcaPcOptions.debug ) { 354 console.log( this.percentage_watched() ) 355 } 356 357 // Need to cover all the cases and reduce interval time -> better performance 358 // Use switch here for better code readable 359 switch ( true ) { 360 // 10% milestone 361 case ( percent >= 10 && percent < 25 ): 362 this.trigger_event('VideoWatch10Percent') 363 break 364 365 // 25% milestone 366 case ( percent >= 25 && percent < 50 ): 367 this.trigger_event('VideoWatch25Percent') 368 break 369 370 case ( percent >= 50 && percent < 75 ): 371 this.trigger_event('VideoWatch50Percent') 372 break 373 374 case ( percent >= 75 && percent < 90 ): 375 this.trigger_event('VideoWatch75Percent') 376 break 377 378 case ( percent >= 90 && percent < 100 ): 379 this.trigger_event('VideoWatch90Percent') 380 break 381 } 382 } 383 384 video_params() { 385 return { 386 video_title: this.title, 387 video_url: this.url, 388 video_id: this.video_id 389 } 390 } 391 392 percentage_watched() { 393 switch( this.type ) { 394 case 'YOUTUBE': 395 return Math.round( this.api.getCurrentTime() / this.api.getDuration() * 100 ) 396 397 case 'VIMEO': 398 this.api.getCurrentTime().then( current_time => { 399 this.current_time = current_time 400 } ) 401 return Math.round( this.current_time / this.duration * 100 ) 402 403 case 'WISTIA': 404 return Math.round( this.api.time() / this.api.duration() * 100 ) 405 406 default: 407 return false 408 } 409 } 410 411 trigger_event( action, additional_params = {} ) { 412 413 if ( this.events_triggered[action] ) { 414 return 415 } 416 417 let params = Object.assign( {}, this.video_params(), additional_params ) 418 419 this.emit( 'pixel_event', 'trackCustom', action, params ) 420 421 // Add exception for VideoPause event 422 if ( action !== 'VideoPause' ) { 423 this.events_triggered[action] = true 424 } 425 } 426 427 428 } 429 430 FCA_PC_Video.prototype.__proto__ = EventEmitter.prototype; 431 432 function onYouTubeIframeAPIReady() { 433 434 fcaPcVideos.forEach(video => { 435 if (video.type !== 'YOUTUBE') { 436 return 437 } 438 439 video.emit('api_youtube_loaded') 440 }) 441 442 } 443 444 window.fcaPcVideos = []; 445 446 // detect all videos on the page 447 function detect_videos() { 448 449 let videos = []; 450 451 jQuery( 'iframe' ).each(function () { 452 var char = jQuery(this)[0].src.includes( '?' ) ? '&' : '?' 453 jQuery(this)[0].src = jQuery(this)[0].src + char + 'enablejsapi=1' 454 videos.push(jQuery(this)) 455 }) 456 457 jQuery( 'script' ).each(function () { 458 459 let src = jQuery(this).attr('src') 460 461 if (typeof src === 'undefined') { 462 return 463 } 464 465 if (src.indexOf('https://fast.wistia.com/embed/medias/') === -1) { 466 return 467 } 468 469 videos.push(jQuery(this)) 470 471 }) 472 473 if (videos.length === 0) { 474 475 if ( fcaPcOptions.debug ) { 476 console.log('No videos detected') 477 } 478 479 return 480 } 481 482 videos.forEach(function (video) { 483 window.fcaPcVideos.push(new FCA_PC_Video(video)) 484 }) 485 486 487 } 488 489 490 detect_videos() 491 492}
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.