Skip to content

Commit

Permalink
Updated playlists model to prevent duplicates.
Browse files Browse the repository at this point in the history
  • Loading branch information
btelliot committed Dec 24, 2024
1 parent 4eb70e0 commit 8739309
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 38 deletions.
113 changes: 75 additions & 38 deletions models/playlists_model.php
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,6 @@ public function resolve($playlist_id, $player_id, $parent_player_id = false, $st
$supports = ['audio','video','image','document'];
}


// TODO currently no player support for documents, but will be doing this at some point.
$support_document = $player_id ? false : true;

Expand Down Expand Up @@ -726,6 +725,11 @@ public function resolve($playlist_id, $player_id, $parent_player_id = false, $st
// track offset for max duration
$media_offset = 0.0;

// Global tracking of used media IDs across all selection types
$used_media_ids = [];
$used_station_ids = [];
$manually_selected_media_ids = []; // Track manually selected media separately

foreach ($playlist_items as $playlist_item) {
if ($playlist_item['properties']) {
$playlist_item['properties'] = json_decode($playlist_item['properties'], true);
Expand All @@ -744,6 +748,7 @@ public function resolve($playlist_id, $player_id, $parent_player_id = false, $st
}

if ($media) {
// Manually selected media is always allowed, even if previously used
$tmp = ['type' => 'media','id' => $playlist_item['item_id'], 'title' => $media['title'], 'artist' => $media['artist']];
if ($media['type'] == 'image') {
$tmp['duration'] = $playlist_item['properties']['duration'];
Expand All @@ -770,6 +775,8 @@ public function resolve($playlist_id, $player_id, $parent_player_id = false, $st
}
}
$media_items_tmp[] = $tmp;
$manually_selected_media_ids[] = $playlist_item['item_id'];
$used_media_ids[] = $playlist_item['item_id'];
}
} elseif ($playlist_item['item_type'] == 'dynamic') {
// dynamic item
Expand All @@ -779,18 +786,27 @@ public function resolve($playlist_id, $player_id, $parent_player_id = false, $st
$media_search = $this->models->media('search', ['params' => ['query' => $playlist_item['properties']['query']], 'player_id' => $player_id]);
$media_items = $media_search[0] ?? [];

// remove unsupported and dayparting exclusions
foreach ($media_items as $index => $media_item) {
if (array_search($media_item['id'], $dayparting_exclude_ids) !== false) {
unset($media_items[$index]);
}
if (!in_array($media_item['type'], $supports)) {
unset($media_items[$index]);
}
// Remove items that are already used in dynamic selection,
// but allow manually selected items to be used in dynamic selection
$media_items = array_filter($media_items, function($media) use ($used_media_ids, $manually_selected_media_ids, $supports, $dayparting_exclude_ids) {
return
// Not used in any previous selection
!in_array($media['id'], $used_media_ids) &&
// Supports the player's media type
in_array($media['type'], $supports) &&
// Not excluded by dayparting
array_search($media['id'], $dayparting_exclude_ids) === false;
});

// Reset used media IDs if all unique items have been exhausted
if (empty($media_items)) {
$media_search = $this->models->media('search', ['params' => ['query' => $playlist_item['properties']['query']], 'player_id' => $player_id]);
$media_items = $media_search[0] ?? [];
$used_media_ids = array_diff($used_media_ids, $manually_selected_media_ids); // Keep manually selected media IDs
}

if (!empty($media_items)) {
// we keep searching until we have enough items. this allows randomization, but will not have two of the same tracks playing nearby each other.
// we keep searching until we have enough items
if ($playlist_item['properties']['num_items']) {
while (count($dynamic_items) < $playlist_item['properties']['num_items']) {
// randomize our items
Expand All @@ -807,6 +823,7 @@ public function resolve($playlist_id, $player_id, $parent_player_id = false, $st
$tmp['media_type'] = $media['type'];
$tmp['context'] = 'Dynamic Selection: ' . $playlist_item['properties']['name'];
$dynamic_items[] = $tmp;
$used_media_ids[] = $media['id'];

// end loop if we have enough items
if (count($dynamic_items) >= $playlist_item['properties']['num_items']) {
Expand All @@ -827,7 +844,9 @@ public function resolve($playlist_id, $player_id, $parent_player_id = false, $st
}
$media_offset += $tmp['duration'];
$tmp['media_type'] = $media['type'];
$tmp['context'] = 'Dynamic Selection: ' . $playlist_item['properties']['name'];
$dynamic_items[] = $tmp;
$used_media_ids[] = $media['id'];
}
}
}
Expand Down Expand Up @@ -864,13 +883,27 @@ public function resolve($playlist_id, $player_id, $parent_player_id = false, $st
$media_items = $this->db->assoc_list();

// remove unsupported and dayparting exclusions
foreach ($media_items as $index => $media) {
if (array_search($media['id'], $dayparting_exclude_ids) !== false) {
unset($media_items[$index]);
}
if (!in_array($media['type'], $supports)) {
unset($media_items[$index]);
}
$media_items = array_filter($media_items, function($media) use ($supports, $dayparting_exclude_ids) {
return in_array($media['type'], $supports) &&
array_search($media['id'], $dayparting_exclude_ids) === false;
});

// Remove already used station IDs
$media_items = array_filter($media_items, function($media) use (&$used_station_ids) {
return !in_array($media['id'], $used_station_ids);
});

// Reset used station IDs if all unique items have been exhausted
if (empty($media_items)) {
$used_station_ids = [];
$this->db->query('SELECT media.* FROM players_station_ids LEFT JOIN media ON players_station_ids.media_id = media.id WHERE player_id="' . $this->db->escape($station_id_player) . '";');
$media_items = $this->db->assoc_list();

// Re-apply filters after reset
$media_items = array_filter($media_items, function($media) use ($supports, $dayparting_exclude_ids) {
return in_array($media['type'], $supports) &&
array_search($media['id'], $dayparting_exclude_ids) === false;
});
}

// randomize our selection
Expand All @@ -894,11 +927,12 @@ public function resolve($playlist_id, $player_id, $parent_player_id = false, $st
$tmp['media_type'] = $media['type'];
$tmp['context'] = 'Station ID';
$media_items_tmp[] = $tmp;
$used_station_ids[] = $media['id'];
}
} elseif ($playlist_item['item_type'] == 'breakpoint') {
$media_items_tmp[] = ['type' => 'breakpoint'];
} elseif ($playlist_item['item_type'] == 'custom') {
// get the callback model/method in order for this custom item, add media items specified by the callback method.
// get the callback model/method in order for this custom item, add media items specified by the callback method.
$custom_item_query = $playlist_item['properties'];
$custom_item_name = $custom_item_query['name'] ?? '';
$this->db->where('name', $custom_item_name);
Expand Down Expand Up @@ -929,38 +963,41 @@ public function resolve($playlist_id, $player_id, $parent_player_id = false, $st
}
}

// add our media items from this run to our complete set of media items.
// Add items to return
$return = array_merge($return, $media_items_tmp);

// break out of loop if we've met our max duration
if ($max_duration && $media_offset >= $max_duration) {
// Check max duration
if ($max_duration !== null && $media_offset > $max_duration) {
break;
}
}

// remove or limit crossfade as required
foreach ($return as $index => &$item) {
// skip if crossfade not set
if ($item['type'] != 'media' || !isset($item['crossfade'])) {
continue;
}
// remove or limit crossfade as required
foreach ($return as $index => &$item) {
// skip if crossfade not set
if ($item['type'] != 'media' || !isset($item['crossfade'])) {
continue;
}

// limit crossfade to item/next-item duration
if (!isset($return[$index + 1])) {
$max_crossfade = $item['duration'];
} else {
$max_crossfade = min($item['duration'], $return[$index + 1]['duration']);
}
if ($item['crossfade'] > $max_crossfade) {
$item['crossfade'] = $max_crossfade;
}
// limit crossfade to item/next-item duration
if (!isset($return[$index + 1])) {
$max_crossfade = $item['duration'];
} else {
$max_crossfade = min($item['duration'], $return[$index + 1]['duration']);
}
if ($item['crossfade'] > $max_crossfade) {
$item['crossfade'] = $max_crossfade;
}

// remove if last track or next track not audio
if ($index == count($return) - 1 || $return[$index + 1]['media_type'] != 'audio') {
unset($item['crossfade']);
// remove if last track or next track not audio
if ($index == count($return) - 1 || $return[$index + 1]['media_type'] != 'audio') {
unset($item['crossfade']);
}
}
}

return $return;
}
}
}
53 changes: 53 additions & 0 deletions test_playlist_resolve.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php
// Include the core components
// This script should be run as php test_playlist_resolve.php 1767 1
// Allows you to test playlist resolution, and duplicate prevention
require_once 'components.php';

// Get the models instance through the framework
$load = OBFLoad::get_instance();
$playlistsModel = $load->model('Playlists');

// Get playlistId and playerId from command-line arguments
if ($argc < 3) {
echo "Usage: php test_playlist_resolve.php <playlistId> <playerId>\n";
exit(1);
}

$playlistId = (int)$argv[1];
$playerId = (int)$argv[2];

// Resolve the playlist
try {
$resolvedItems = $playlistsModel('resolve',
$playlistId, // Specific playlist ID to test
$playerId, // Player ID
false, // No parent player
new DateTime(), // Start time
null // Max duration
);

// Print out resolved items in simplified format
echo "Resolved Playlist Items:\n";
foreach ($resolvedItems as $item) {
echo "{$item['context']}, {$item['id']}\n";
}

// Check for duplicates manually
$mediaIds = array_column($resolvedItems, 'id');
$duplicates = array_diff_assoc($mediaIds, array_unique($mediaIds));

echo "\nDuplicates Found: ";
if (empty($duplicates)) {
echo "None\n";
} else {
echo implode(", ", $duplicates) . "\n";
}

// Additional analysis
echo "\nTotal Items: " . count($resolvedItems) . "\n";
echo "Unique Items: " . count(array_unique($mediaIds)) . "\n";

} catch (Exception $e) {
echo "Error resolving playlist: " . $e->getMessage() . "\n";
}

0 comments on commit 8739309

Please sign in to comment.