1/** 2 * Audio Advertisement Manager for video.js Player 3 * Handles ad injection, control disabling, and playback switching 4 */ 5 6(function () { 7 "use strict"; 8 9 let advertisementManager = { 10 activeAds: [], 11 currentSongPlayTime: 0, // Time played for CURRENT song only 12 currentSongId: null, // Track which song is playing 13 adPlayedForCurrentSong: false, // Track if ad already played for current song 14 isPlayingAd: false, 15 currentAdIndex: -1, 16 pausedSongIndex: -1, 17 pausedSongTime: 0, 18 playbackCheckInterval: null, 19 skipCountdownInterval: null, // For skip countdown timer 20 shouldInjectAd: true, 21 22 init: function () { 23 this.fetchActiveAds(); 24 this.bindPlayerEvents(); 25 }, 26 27 fetchActiveAds: function () { 28 const self = this; 29 fetch("/api/advertisements/active") 30 .then((response) => { 31 if (!response.ok) { 32 throw new Error("HTTP error! status: " + response.status); 33 } 34 return response.json(); 35 }) 36 .then((data) => { 37 console.log("API Response:", data); 38 if (data.status === 1) { 39 self.activeAds = data.data; 40 console.log( 41 "Loaded " + self.activeAds.length + " active advertisements" 42 ); 43 console.log("Advertisements:", self.activeAds); 44 } else { 45 console.error("API returned error status:", data); 46 } 47 }) 48 .catch((error) => { 49 console.error("Failed to load advertisements:", error); 50 }); 51 }, 52 53 bindPlayerEvents: function () { 54 const self = this; 55 56 if (typeof gmoplayer !== "undefined") { 57 // Track playback time every second for CURRENT song 58 self.playbackCheckInterval = setInterval(function () { 59 if (!gmoplayer.paused() && !self.isPlayingAd) { 60 const currentIndex = gmoplayer.playlist.currentIndex(); 61 const currentSong = gmoplayer.playlist()[currentIndex]; 62 const currentSongId = currentSong 63 ? currentSong.song_id || currentSong.id 64 : null; 65 66 // If song changed, reset timer AND ad-played flag 67 if (self.currentSongId !== currentSongId) { 68 console.log("Song changed - resetting timer"); 69 console.log("Previous song ID:", self.currentSongId); 70 console.log("New song ID:", currentSongId); 71 console.log( 72 "New song:", 73 currentSong ? currentSong.title : "Unknown" 74 ); 75 self.currentSongPlayTime = 0; 76 self.currentSongId = currentSongId; 77 self.adPlayedForCurrentSong = false; // Reset ad flag for new song 78 } 79 80 // Track time for ALL songs (including live radio) 81 self.currentSongPlayTime++; 82 83 // Log every 10 seconds 84 if (self.currentSongPlayTime % 10 === 0) { 85 console.log( 86 "Current song play time: " + 87 self.currentSongPlayTime + 88 " seconds for: " + 89 (currentSong ? currentSong.title : "Unknown") + 90 " | Ad played: " + 91 self.adPlayedForCurrentSong 92 ); 93 } 94 95 self.checkIfAdShouldPlay(); 96 } 97 }, 1000); 98 99 console.log("Advertisement manager tracking per-song playback time"); 100 } 101 }, 102 103 // Called when a normal song ends naturally (not ads) 104 onSongEnded: function () { 105 console.log("onSongEnded: Song finished, moving to next track"); 106 107 // Move to next track in playlist 108 if (gmoplayer && gmoplayer.playlist) { 109 const currentIndex = gmoplayer.playlist.currentIndex(); 110 const playlistLength = gmoplayer.playlist().length; 111 112 console.log( 113 "Current index:", 114 currentIndex, 115 "Playlist length:", 116 playlistLength 117 ); 118 119 // Move to next track 120 const nextIndex = currentIndex + 1; 121 122 if (nextIndex < playlistLength) { 123 // There's a next track 124 console.log("Moving to next track at index:", nextIndex); 125 gmoplayer.playlist.next(); 126 } else { 127 // End of playlist, go to first track 128 console.log("End of playlist, going to first track"); 129 gmoplayer.playlist.currentItem(0); 130 } 131 132 // Make sure it plays 133 setTimeout(function () { 134 gmoplayer.play(); 135 }, 100); 136 } 137 }, 138 139 checkIfAdShouldPlay: function () { 140 if (this.activeAds.length === 0) return; 141 if (this.isPlayingAd) return; 142 if (this.currentSongPlayTime === 0) return; // Don't check at 0 seconds 143 if (this.adPlayedForCurrentSong) return; // Don't play ad again for same song 144 145 // Get the ad with highest priority that should play
146 const adToPlay = this.activeAds.find((ad) => { 147 if (ad.play_frequency_type === "song_based") { 148 // For song_based, use play_frequency_value as MINUTES 149 const frequencyInSeconds = ad.play_frequency_value * 60; 150 // EXACT match - play ONCE when time equals frequency 151 const shouldPlay = this.currentSongPlayTime === frequencyInSeconds; 152 153 if (shouldPlay) { 154 console.log("=== AD TRIGGER ==="); 155 console.log("Ad: " + ad.title); 156 console.log( 157 "Frequency: After " + ad.play_frequency_value + " minutes" 158 ); 159 console.log( 160 "Current song play time: " + this.currentSongPlayTime + " seconds" 161 ); 162 } 163 164 return shouldPlay; 165 } else if (ad.play_frequency_type === "time_based") { 166 // For time_based, use play_frequency_value as SECONDS 167 // EXACT match - play ONCE when time equals frequency 168 const shouldPlay = 169 this.currentSongPlayTime === ad.play_frequency_value; 170 171 if (shouldPlay) { 172 console.log("=== AD TRIGGER ==="); 173 console.log("Ad: " + ad.title); 174 console.log( 175 "Frequency: After " + ad.play_frequency_value + " seconds" 176 ); 177 console.log( 178 "Current song play time: " + this.currentSongPlayTime + " seconds" 179 ); 180 } 181 182 return shouldPlay; 183 } 184 return false; 185 }); 186 187 if (adToPlay) { 188 this.adPlayedForCurrentSong = true; // Mark ad as played for this song 189 this.playAdvertisement(adToPlay); 190 } 191 }, 192 193 playAdvertisement: function (ad) { 194 if (!gmoplayer || this.isPlayingAd) return; 195 196 console.log("=== INTERRUPTING CURRENT SONG FOR ADVERTISEMENT ==="); 197 console.log("Ad Title:", ad.title); 198 console.log("Ad Duration:", ad.duration + " seconds"); 199 console.log("Ad URL:", ad.audio_url); 200 201 // Save current song state BEFORE pausing 202 this.pausedSongIndex = gmoplayer.playlist.currentIndex(); 203 this.pausedSongTime = gmoplayer.currentTime(); 204 const pausedPlaylist = gmoplayer.playlist(); 205 const pausedSong = pausedPlaylist[this.pausedSongIndex]; 206 this.isPlayingAd = true; 207 208 console.log("Saving current state:"); 209 console.log("- Song:", pausedSong.title); 210 console.log("- Index:", this.pausedSongIndex); 211 console.log("- Time:", this.pausedSongTime + " seconds"); 212 213 // Pause current song 214 gmoplayer.pause(); 215 216 // Load and play advertisement directly (no playlist modification) 217 gmoplayer.src({ 218 src: ad.audio_url, 219 type: "audio/mpeg", 220 }); 221 222 console.log("Loading advertisement..."); 223 224 // Wait for ad to load, then play 225 const self = this; 226 gmoplayer.one("loadedmetadata", function () { 227 console.log("Advertisement loaded - starting playback"); 228 gmoplayer.play(); 229 }); 230 231 // When ad ends, restore the original song 232 gmoplayer.one("ended", function () { 233 console.log("Advertisement ended - restoring song"); 234 self.adEnded(pausedSong, self.pausedSongTime); 235 }); 236 237 // Disable controls 238 this.disableControls(); 239 240 // Show ad indicator 241 this.showAdIndicator(ad); 242 243 console.log("=== ADVERTISEMENT PLAYING ==="); 244 }, 245 246 disableControls: function () { 247 // Disable skip buttons 248 $(".jp-previous").prop("disabled", true).addClass("disabled"); 249 $(".jp-next").prop("disabled", true).addClass("disabled"); 250 $(".jp-skip-back").prop("disabled", true).addClass("disabled"); 251 $(".jp-skip-forward").prop("disabled", true).addClass("disabled"); 252 253 // Disable seek/progress slider 254 $(".progress-slider").prop("disabled", true).addClass("disabled"); 255 256 // Disable shuffle and repeat 257 $(".jp-shuffle").prop("disabled", true).addClass("disabled"); 258 $(".jp-repeat").prop("disabled", true).addClass("disabled"); 259 260 // Hide download and other options 261 $(".play_song_options").hide(); 262 $(".jp_queue_wrapper").hide(); 263 }, 264 265 enableControls: function () { 266 // Enable skip buttons 267 $(".jp-previous").prop("disabled", false).removeClass("disabled"); 268 $(".jp-next").prop("disabled", false).removeClass("disabled"); 269 $(".jp-skip-back").prop("disabled", false).removeClass("disabled"); 270 $(".jp-skip-forward").prop("disabled", false).removeClass("disabled"); 271 272 // Enable seek/progress slider 273 $(".progress-slider").prop("disabled", false).removeClass("disabled"); 274 275 // Enable shuffle and repeat 276 $(".jp-shuffle").prop("disabled", false).removeClass("disabled"); 277 $(".jp-repeat").prop("disabled", false).removeClass("disabled"); 278 279 // Show download and other options 280 $(".play_song_options").show(); 281 $(".jp_queue_wrapper").show(); 282 }, 283 284 showAdIndicator: function (ad) { 285 const self = this; 286 287 // Add visual indicator that an ad is playing 288 const adBanner = ` 289 <div id="ad-playing-banner" style="background: #dc143c; color: white; padding: 10px; text-align: center; position: fixed; top: 0; left: 0; right: 0; z-index: 9999;"> 290 <strong>Advertisement</strong> - Playing: ${ad.title} (${ 291 ad.duration 292 }s) 293 ${ 294 ad.is_skippable 295 ? '<span id="skip-ad-message" style="margin-left: 20px;">Skip available in <span id="skip-countdown">5</span> seconds</span><button id="skip-ad-btn" style="display: none; margin-left: 20px; padding: 5px 15px; background: white; color: #dc143c; border: none; border-radius: 3px; cursor: pointer; font-weight: bold;">Skip Ad âº</button>' 296 : '<span style="margin-left: 20px;">Cannot skip</span>' 297 } 298 </div> 299 `; 300 301 if ($("#ad-playing-banner").length === 0) { 302 $("body").prepend(adBanner); 303 } 304 305 // If ad is skippable, start countdown 306 if (ad.is_skippable) { 307 let countdown = 5; 308 const countdownInterval = setInterval(function () { 309 countdown--; 310 $("#skip-countdown").text(countdown); 311 312 if (countdown <= 0) { 313 clearInterval(countdownInterval); 314 // Show skip button 315 $("#skip-ad-message").hide(); 316 $("#skip-ad-btn").show(); 317 } 318 }, 1000); 319 320 // Store interval ID so we can clear it if needed 321 this.skipCountdownInterval = countdownInterval; 322 323 // Add click handler for skip button 324 $(document).on("click", "#skip-ad-btn", function () { 325 console.log("Skip button clicked - ending advertisement"); 326 clearInterval(self.skipCountdownInterval); 327 328 // Get current paused song info before ending 329 const pausedSong = gmoplayer.playlist()[self.pausedSongIndex]; 330 const pausedTime = self.pausedSongTime; 331 332 // Stop the ad 333 gmoplayer.pause(); 334 335 // Restore the song 336 self.adEnded(pausedSong, pausedTime); 337 }); 338 } 339 }, 340 341 hideAdIndicator: function () { 342 // Clear countdown interval if exists 343 if (this.skipCountdownInterval) { 344 clearInterval(this.skipCountdownInterval); 345 this.skipCountdownInterval = null; 346 } 347 348 // Remove click handler
349 $(document).off("click", "#skip-ad-btn"); 350 351 // Remove banner 352 $("#ad-playing-banner").remove(); 353 }, 354 355 adEnded: function (pausedSong, pausedTime) { 356 console.log("=== RESTORING ORIGINAL SONG ==="); 357 console.log("Restoring:", pausedSong.title); 358 console.log("At time:", pausedTime + " seconds"); 359 360 // Restore controls 361 this.enableControls(); 362 this.hideAdIndicator(); 363 364 // Load the original song back 365 gmoplayer.src(pausedSong.sources[0]); 366 gmoplayer.poster(pausedSong.poster); 367 368 // When loaded, seek to saved position and play 369 gmoplayer.one("loadedmetadata", function () { 370 gmoplayer.currentTime(pausedTime); 371 gmoplayer.play(); 372 console.log("Song resumed from " + pausedTime + " seconds"); 373 }); 374 375 // Reset state - keep currentSongId and adPlayedForCurrentSong 376 this.isPlayingAd = false; 377 this.pausedSongIndex = -1; 378 this.pausedSongTime = 0; 379 // DON'T reset currentSongPlayTime, currentSongId, or adPlayedForCurrentSong 380 // We're resuming the same song and ad has already played for it 381 382 console.log("=== SONG RESTORED ==="); 383 }, 384 }; 385 386 // Initialize when document is ready 387 $(document).ready(function () { 388 console.log("Document ready - Initializing advertisement manager..."); 389 390 // Wait for player to be ready 391 let initAttempts = 0; 392 const maxAttempts = 10; 393 394 const tryInit = function () { 395 initAttempts++; 396 397 if (typeof gmoplayer !== "undefined" && gmoplayer.playlist) { 398 advertisementManager.init(); 399 console.log("â Advertisement Manager initialized successfully"); 400 console.log("â Player found and ready"); 401 } else { 402 console.log( 403 "Attempt " + 404 initAttempts + 405 "/" + 406 maxAttempts + 407 ": Waiting for gmoplayer..." 408 ); 409 410 if (initAttempts < maxAttempts) { 411 setTimeout(tryInit, 1000); 412 } else { 413 console.error( 414 "â gmoplayer not found after " + 415 maxAttempts + 416 " attempts. Advertisement manager cannot initialize." 417 ); 418 } 419 } 420 }; 421 422 setTimeout(tryInit, 2000); 423 }); 424 425 // Make it globally accessible 426 window.advertisementManager = advertisementManager; 427})();
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.