PageSourceSearch

http://disrupt.radio/assets/js/advertisement-manager.js

js disrupt.radio collected 2026-10-02 14:30:34 UTC 14,542 bytes, 427 lines download raw bytes

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.