1var DEBUG = false; 2var audio_groups = []; 3 4var SEL_GROUP = '.audio-sync-group'; 5var SEL_BUFFERING_LBL = '.audio-sync-buffering-label'; 6var SEL_PLAYING_LBL = '.audio-sync-playing-label'; 7var SEL_AUDIO_A = '.audio-sync-a'; 8var SEL_AUDIO_B = '.audio-sync-b'; 9var SEL_PLAY_BTN = '.audio-btn-sync-play'; 10var SEL_AUDIO_A_BTN = '.audio-btn-sync-a'; 11var SEL_AUDIO_B_BTN = '.audio-btn-sync-b'; 12 13var CLS_BUFFERING = 'audio-sync-buffering'; 14var CLS_BUFFERED = 'audio-sync-buffered'; 15var CLS_PLAYING = 'audio-sync-playing'; 16var CLS_PLAYING_A = 'audio-sync-playing-a'; 17var CLS_PLAYING_B = 'audio-sync-playing-b'; 18 19var DATASET_AUDIO_VOLUME = 'audioSyncVolume'; 20var DATASET_TOGGLE_TIMER = 'audioSyncToggleTimer'; 21var DATASET_BUFFERING_TEXT = 'audioSyncBufferingText'; 22var DATASET_BUFFERED_TEXT = 'audioSyncBufferedText'; 23var DATASET_PLAYING_A_TEXT = 'audioSyncPlayingAText'; 24var DATASET_PLAYING_B_TEXT = 'audioSyncPlayingBText'; 25 26// Entry point & sanitization 27jQuery(document).ready(function() { 28 // Banner 29 debug(' ___ _______ ___ _ _ _____ \n' + 30 ' / _ \\ / / ___ \\ / _ \\ | (_) / ___| \n' + 31 '/ /_\\ \\ / /| |_/ / / /_\\ \\_ _ __| |_ ___ \\ `--. _ _ _ __ ___ \n' + 32 '| _ | / / | ___ \\ | _ | | | |/ _` | |/ _ \\ `--. \\ | | | \'_ \\ / __| \n' + 33 '| | | |/ / | |_/ / | | | | |_| | (_| | | (_) | /\\__/ / |_| | | | | (__ \n' + 34 '\\_| |_/_/ \\____/ \\_| |_/\\__,_|\\__,_|_|\\___/ \\____/ \\__, |_| |_|\\___| \n' + 35 ' __/ | \n' + 36 ' |___/ \n'); 37 38 // Find all audio groups 39 var groups = jQuery(SEL_GROUP); 40 debug('Audio groups found: ' + groups.length); 41 42 jQuery.each(groups, function(gid, group) { 43 debug('groups[' + gid + ']: ' + group); 44 45 // Find the play button(s) 46 var btnPlay = jQuery(group).find(SEL_PLAY_BTN); 47 if( btnPlay === null || btnPlay.length <= 0 ) { 48 debug('Group[' + gid + '] does not have a play button!'); 49 return; 50 } 51 52 // Find the "A" button 53 var btnA = jQuery(group).find(SEL_AUDIO_A_BTN); 54 if( btnA === null || btnA.length <= 0 ) { 55 debug('Group[' + gid + '] does not have an "A" button'); 56 } 57 58 // Find the "B" button 59 var btnB = jQuery(group).find(SEL_AUDIO_B_BTN); 60 if( btnB === null || btnB.length <= 0 ) { 61 debug('Group[' + gid + '] does not have a "B" button'); 62 } 63 64 // Find the "A" audio track 65 var audioA = jQuery(group).find(SEL_AUDIO_A); 66 if( audioA === null || audioA.length <= 0 ) { 67 debug('Group[' + gid + '] does not have an "A" audio track!'); 68 return; 69 } else if( audioA.length > 1 ) { 70 debug('Group[' + gid + '] has too many "A" audio tracks!'); 71 return; 72 } 73 74 // Find the "B" audio track 75 var audioB = jQuery(group).find(SEL_AUDIO_B); 76 if( audioB === null || audioB.length <= 0 ) { 77 debug('Group[' + gid + '] does not have an "B" audio track!'); 78 return; 79 } else if( audioB.length > 1 ) { 80 debug('Group[' + gid + !'] has too many "B" audio tracks!'); 81 return; 82 } 83 84 var soundA, soundB; 85 if( audioA.is('a') ) { 86 debug('Using link (href) object for audio "A"'); 87 soundA = audioA.attr('href'); 88 if( !soundA ) { 89 debug('Missing audio file for audio "A" link'); 90 return; 91 } 92 } else if( audioA.is('audio') ) { 93 debug('Using HTML5 audio (src) object for audio "A"'); 94 soundA = audioA[0].src; 95 if( !soundA ) { 96 debug('Missing audio file for audio "A" HTML5 audio object'); 97 return; 98 } 99 audioA.hide(); 100 } else { 101 debug('Could not find a usable audio file for audio "A"'); 102 return; 103 } 104 if( audioB.is('a') ) { 105 debug('Using link (href) object for audio "B"'); 106 soundB = audioB.attr('href'); 107 if( !soundB ) { 108 debug('Missing audio file for audio "B" link'); 109 return; 110 } 111 } else if( audioB.is('audio') ) { 112 debug('Using HTML5 audio (src) object for audio "B"'); 113 soundB = audioB[0].src; 114 if( !soundB ) { 115 debug('Missing audio file for audio "B" HTML5 audio object'); 116 return; 117 } 118 audioB.hide(); 119 } else { 120 debug('Could not find a usable audio file for audio "B"'); 121 return; 122 } 123 124 if( (device.mobile() || device.tablet()) && device.ios() ) { 125 debug('Apple iOS detected. Disabling audio auto-enable'); 126 Howler.mobileAutoEnable = false;
127 } 128 129 debug('AudioA: Using audio source: ' + soundA); 130 soundA = new Howl({ 131 src: [soundA], 132 volume: parseInt(audioA[0].dataset[DATASET_AUDIO_VOLUME] || 100) / 100, 133 onload: function() { debug('AudioA "onload" event!'); }, 134 onloaderror: function(id, err) { debug('AudioA "onloaderror" event! (err=' + err + ')'); }, 135 onplay: function() { debug('AudioA "onplay" event!'); }, 136 onend: function() { debug('AudioA "onend" event!'); }, 137 onpause: function() { debug('AudioA "onpause" event!'); }, 138 onstop: function() { debug('AudioA "onstop" event!'); }, 139 onmute: function() { debug('AudioA "onmute" event!'); }, 140 onvolume: function() { debug('AudioA "onvolume" event!'); }, 141 onrate: function() { debug('AudioA "onrate" event!'); }, 142 onseek: function() { debug('AudioA "onseek" event!'); }, 143 onfade: function() { debug('AudioA "onfade" event!'); } 144 }); 145 debug('AudioB: Using audio source: ' + soundB); 146 soundB = new Howl({ 147 src: [soundB], 148 volume: parseInt(audioB[0].dataset[DATASET_AUDIO_VOLUME] || 100) / 100, 149 onload: function() { debug('AudioB "onload" event!'); }, 150 onloaderror: function(id, err) { debug('AudioB "onloaderror" event! (err=' + err + ')'); }, 151 onplay: function() { debug('AudioB "onplay" event!'); }, 152 onend: function() { debug('AudioB "onend" event!'); }, 153 onpause: function() { debug('AudioB "onpause" event!'); }, 154 onstop: function() { debug('AudioB "onstop" event!'); }, 155 onmute: function() { debug('AudioB "onmute" event!'); }, 156 onvolume: function() { debug('AudioB "onvolume" event!'); }, 157 onrate: function() { debug('AudioB "onrate" event!'); }, 158 onseek: function() { debug('AudioB "onseek" event!'); }, 159 onfade: function() { debug('AudioB "onfade" event!'); } 160 }); 161 162 // Web Audio is effectively muted on iOS mobile without user interaction 163 if( (device.mobile() || device.tablet()) && device.ios() ) { 164 debug('Adding EventListener(touchend)'); 165 window.addEventListener('touchend', unlockIOSAudioPlayback); 166 } 167 168 // Enable button once audio sources have loaded 169 handleBufferingState(group, false); 170 soundA.once('load', function() { 171 debug('audio[' + gid + '].soundA has loaded'); 172 if( soundA.state() === 'loaded' ) 173 handleBufferingState(group, true); 174 }); 175 soundB.once('load', function() { 176 debug('audio[' + gid + '].soundB has loaded'); 177 if( soundA.state() === 'loaded' ) 178 handleBufferingState(group, true); 179 }); 180 181 audio_groups.push({ 182 // jQuery group object 183 group: group, 184 // jQuery play button object 185 btnPlay: btnPlay, 186 // jQuery toggle button objects 187 btnA: btnA, 188 btnB: btnB, 189 // Web audio objects 190 audioA: audioA, 191 audioA_sound: soundA, 192 audioB: audioB, 193 audioB_sound: soundB, 194 }); 195 }); 196 197 // Check that at least one audio group exists 198 if( audio_groups === null || audio_groups.length <= 0 ) { 199 debug('No usable audio groups found'); 200 return; 201 } 202 203 // Disable all audio buttons in each group 204 debug('Disabling group buttons'); 205 groups.find(SEL_PLAY_BTN).attr('disabled', 'true'); 206 groups.find(SEL_AUDIO_A_BTN).attr('disabled', 'true'); 207 groups.find(SEL_AUDIO_B_BTN).attr('disabled', 'true'); 208 debug('Starting ABAudioSync()'); 209 ABAudioSync(); 210}); 211 212var unlockIOSAudioPlayback = function() { 213 var ctx = Howler.ctx; 214 debug('Attempting to enable iOS Web Audio.'); 215 // create empty buffer 216 var buffer = ctx.createBuffer(1, 22050, 22050); 217 var source = ctx.createBufferSource(); 218 source.buffer = buffer; 219 // connect to output (your speakers) 220 source.connect(ctx.destination); 221 // play the object 222 if (typeof source.noteOn === 'undefined') { 223 source.start(0); 224 } else { 225 source.noteOn(0); 226 } 227 // Wait to remove the listener 228 source.onended = function() { 229 debug('iOS unlock state (onended): ' + source); 230 window.removeEventListener('touchend', unlockIOSAudioPlayback); 231 }; 232}; 233 234 235function handleBufferingState(group, buffered) { 236 if( !buffered ) { 237 jQuery(group).find(SEL_BUFFERING_LBL)
238 .text(group.dataset[DATASET_BUFFERING_TEXT] || 'Buffering audio...') 239 .addClass(CLS_BUFFERING); 240 } else { 241 jQuery(group).find(SEL_BUFFERING_LBL) 242 .text(group.dataset[DATASET_BUFFERED_TEXT] || '') 243 .addClass(CLS_BUFFERED); 244 jQuery(group).find(SEL_PLAY_BTN).attr('disabled', false); 245 } 246} 247 248 249function ABAudioSync() { 250 jQuery.each(audio_groups, function(aid, audio) { 251 debug('Setting Audio Group #' + aid + ' Play button click handlers'); 252 jQuery.each(audio.btnPlay, function(bid, btn) { 253 jQuery(btn).click({group: aid, btn: bid}, btnPlay_Clicked); 254 }); 255 256 if( audio.btnA ) { 257 debug('Setting Audio Group #' + aid + ' "A" button click handlers'); 258 jQuery.each(audio.btnA, function(bid, btn) { 259 jQuery(btn).click({group: aid, btn: bid}, btnA_Clicked); 260 }); 261 } 262 263 if( audio.btnB ) { 264 debug('Setting Audio Group #' + aid + ' "B" button click handlers'); 265 jQuery.each(audio.btnB, function(bid, btn) { 266 jQuery(btn).click({group: aid, btn: bid}, btnB_Clicked); 267 }); 268 } 269 }); 270} 271 272var timer = Array(); 273 274function btnPlay_Clicked(event) { 275 debug('audio[' + event.data.group + '].btnPlay[' + event.data.btn + '] was clicked'); 276 var gid = event.data.group; 277 var group = audio_groups[gid]; 278 279 // Clear any timers active 280 if( timer[gid] ) clearInterval(timer[gid]); 281 282 if( group.audioA_sound.playing() || group.audioB_sound.playing() ) { 283 // Stop all 284 group.audioA_sound.stop(); 285 group.audioB_sound.stop(); 286 // Clear any end handlers 287 group.audioA_sound.off('end'); 288 // Disable buttons 289 group.btnPlay.text('Play'); 290 group.btnA.attr('disabled','true'); 291 group.btnB.attr('disabled','true'); 292 group.btnA.removeClass(CLS_PLAYING); 293 group.btnB.removeClass(CLS_PLAYING); 294 jQuery(group.group).find(SEL_PLAYING_LBL) 295 .text('').removeClass(CLS_PLAYING_A).removeClass(CLS_PLAYING_B); 296 } else { 297 // If the track ends, "press" the stop button 298 group.audioA_sound.once('end', function() { btnPlay_Clicked(event); }); 299 // Enable buttons 300 group.btnPlay.text('Stop'); 301 group.btnA.attr('disabled','true'); 302 group.btnB.removeAttr('disabled'); 303 jQuery(group.group).find(SEL_PLAYING_LBL) 304 .text(group.group.dataset[DATASET_PLAYING_A_TEXT] || 'Playing track A') 305 .addClass(CLS_PLAYING_A).removeClass(CLS_PLAYING_B); 306 // Play audioA 307 group.audioB_sound.stop(); 308 group.audioA_sound.stop(); 309 group.audioA_sound.play(); 310 // Take care of any toggle timers defined 311 if( group.group.dataset[DATASET_TOGGLE_TIMER] ) { 312 var timer_s = parseInt(group.group.dataset[DATASET_TOGGLE_TIMER]); 313 debug('Starting ' + timer_s + 's toggle timer for group[' + gid + ']!'); 314 (function(group, gid) { 315 timer[gid] = setInterval(function() { 316 debug('Switching tracks of group[' + gid + ']'); 317 switchTracks(group); 318 }, timer_s * 1000); 319 })(group, gid); 320 } 321 } 322} 323 324function switchTracks(group) { 325 if( !group.audioA_sound.playing() ) { 326 // Switch tracks 327 debug('Switching tracks: AudioB -> AudioA'); 328 group.btnA.attr('disabled','true'); 329 group.btnB.removeAttr('disabled'); 330 group.btnB.removeClass(CLS_PLAYING); 331 group.btnA.addClass(CLS_PLAYING); 332 jQuery(group.group).find(SEL_PLAYING_LBL) 333 .text(group.group.dataset[DATASET_PLAYING_A_TEXT] || 'Playing track A') 334 .addClass(CLS_PLAYING_A).removeClass(CLS_PLAYING_B); 335 group.audioA_sound.seek(group.audioB_sound.seek()); 336 group.audioB_sound.stop(); 337 group.audioA_sound.play(); 338 } else { 339 // Switch tracks 340 debug('Switching tracks: AudioA -> AudioB'); 341 group.btnB.attr('disabled','true'); 342 group.btnA.removeAttr('disabled'); 343 group.btnA.removeClass(CLS_PLAYING); 344 group.btnB.addClass(CLS_PLAYING); 345 jQuery(group.group).find(SEL_PLAYING_LBL) 346 .text(group.group.dataset[DATASET_PLAYING_B_TEXT] || 'Playing track B') 347 .addClass(CLS_PLAYING_B).removeClass(CLS_PLAYING_A); 348 group.audioB_sound.seek(group.audioA_sound.seek()); 349 group.audioA_sound.stop(); 350 group.audioB_sound.play(); 351 } 352} 353 354function btnA_Clicked(event) { 355 debug('audio[' + event.data.group + '].btnA[' + event.data.btn + '] was clicked'); 356 switchTracks(audio_groups[event.data.group]); 357} 358 359function btnB_Clicked(event) { 360 debug('audio[' + event.data.group + '].btnB[' + event.data.btn + '] was clicked'); 361 switchTracks(audio_groups[event.data.group]); 362} 363 364function debug(msg) { 365 if( DEBUG ) 366 console.debug(msg); 367}
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.