Playlists: Option to avoid scheduling repeating tracks for xx time before/after Show Start (UPDATE)
  • ****First of all i have version 2.5.1 so everything i did applies to this version.*****

    ****Always keep backup of your files before you use the files i attached!! Everything needs testing! Try everything with your responsibility. Have in mind that since you are inserting values in the database, if you use your old pages in the future again then you may experience problems in library (you will need then to delete the records from the db that are inserted from this hack)****

    UPDATED!! Find the solution for FUTURE shows at the 3rd post!!!

    THE FILES WERE UPLOADED BY MISTAKE AND ARE INTENDED FOR THE JINGLES HACK!! PLEASE DONT USE THEM! (SORRY CANT DELETE THEM :( )

    Well after i started messing around with the library prevous week, i desided to try to solve a common (i believe) problem (well not the best solution...read above the bold letters):
    When you create a random Playlist/Smartblock with criteria (dynamic or static) airtime doesnt take into account if each item is already scheduled even in the next minutes. Especially in dynamic blocks its a very serious problem since if you have many shows within a day and a song meets the criteria in all of them, theoreticaly (especially if you dont have a big number of songs) it could repeat to every single show and play many times within a day.

    The first (and easy) thing i did is create an option to library page (new or edit block) that looks like the "limit to". It has a textbox that takes a number and a dropdown menu with options "hours" and "minutes" and store it in the database along with the other criteria. After that i tried many things that most of them worked right away but many of the were too slow to give as a solution.
    First i used this new value and checked that the "last played" date of the song is older than today minus the hours (or minutes) that we chose.
    This worked ok but its not a real solution because we dont check future schedules (and actually we dont check schedules at all. the "last played" information is directly accessible within the files table).
    Then i went to the function "getListOfFilesUnderLimit" inside the Block.php (ofcourse every time i messed with many other files in order for each solution to work but just to give you a general idea). From what i understood this is the function that is used AFTER you get the list of the files that meet the criteria and you create the block that doesnt overlap the limit you have (for example 2 hours). 
    There for every song i did a query to the cc_schedule table and checked if it has a record that the "starts" column is within the "ShowStart - hours" and "ShowStart + hours". This worked great but had one major and one minor problem. The major problem was that it was much slower than the original code (and its logican since for each item you do another loop) and the minor problem was that the counter for the final items (the number that you see and indicates the number that meets the criteria) was wrong since this is created before this function.
    To make this sorter, i did many many many other experiments some a lot faster but not great enough and some with the same results.
    Finally i went to the function "getListofFilesMeetCriteria" (should i really explain the name? :P) and desided to work there.
    I put a left join to the schedule table to do my checks and to overcome the problem that i would take the same song over and over again (repeating in the schedule table) i groupped the final results according to the song id to get distinct results. After that i could just query to get every file that meets the criteria AND if check that the file is not scheduled the xx hours (that we set) before or after ShowStart. This works reasonably fast and I THINK gives correct results (not 100% sure so whoever tries this do it in a development server and do your tests after ofcourse keeping a backup of everything!).

    Here is what i did (from what i remember since i did many experiments and i am not good at keeping exact track to what i do)
    The names of the functions and variables are LPxx because i first intented to use "last play".. change it to whatever you like
    Post edited by Stathis Simos at 2015-12-15 18:49:47
  • 9 Comments sorted by
  • 1) File /usr/share/airtime/application/forms/SmartBlockCriteria.php

    Under the function getLimitOptions I Created the function:

    private function getLPOptions()
        {
            if (!isset($this->limitOptions)) {
                $this->limitOptions = array(
                    "hours"   => _("hours"),
                    "minutes" => _("minutes")
                );
            }
            return $this->limitOptions;
        }

    within the function "startForm" and between the $limitValue = ... and $bl = ... i put this code

    $lp = new Zend_Form_Element_Select('sp_lp_options');
            $lp->setAttrib('class', 'sp_input_select')
                  ->setDecorators(array('viewHelper'))
                  ->setMultiOptions($this->getLPOptions());
            if (isset($storedCrit["lp"])) {
                $lp->setValue($storedCrit["lp"]["modifier"]);
            }
            $this->addElement($lp);

    $lpValue = new Zend_Form_Element_Text('sp_lp_value');
            $lpValue->setAttrib('class', 'sp_input_text_limit')
                       ->setLabel(_('Check Track schedules'))
                       ->setDecorators(array('viewHelper'));
            $this->addElement($lpValue);
            if (isset($storedCrit["lp"])) {
                $lpValue->setValue($storedCrit["lp"]["value"]);
            } else {
                // setting default to 24 hour
               $lpValue->setValue(24);
            }


    Within the function "isValid" and above the line "if (isset($data['criteria'])) {" i put this:

    $multiplier = 1;
            if ($data['etc']['sp_lp_options'] == 'hours') {
                $multiplier = 60;
            }
            if ($data['etc']['sp_lp_options'] == 'hours' || $data['etc']['sp_lp_options'] == 'mins') {
                $element = $this->getElement("sp_lp_value");
                if ($data['etc']['sp_lp_value'] == "" || floatval($data['etc']['sp_lp_value']) < 0) {
                    $element->addError(_("Cannot be empty or smaller than 0"));
                    $isValid = false;
                }
            }

    2) File /usr/share/airtime/public/css/styles.css

    changed the .sp_text_font class to
    .sp_text_font{
        font-size: 13px;
        font-family: Helvetica, Arial, sans-serif;
        color: #5B5B5B;
        width: 160px;
        display: -webkit-inline-box;
    }

    Do whatever change you like yourself.

    3) File /usr/share/airtime/application/models/Block.php

    Within function "storeCriteriaIntoDb" between the // insert limit info .....save() and // insert repeate track option .... save()
    I put this
            // insert lp info
            $qry = new CcBlockcriteria();
            $qry->setDbCriteria("lp")
            ->setDbModifier($p_criteriaData['etc']['sp_lp_options'])
            ->setDbValue($p_criteriaData['etc']['sp_lp_value'])
            ->setDbBlockId($this->id)
            ->save();

    Within function getListofFilesMeetCriteria and under the $qry->useFkOwnerQuery("subj", "left join"); i put:

    $qry->useCcScheduleQuery("schedule", "left join");

    Above the "$repeatTracks = 0;" within the same function i put:

    $lps = array();
            if (isset($storedCrit['lp'])) {
                $lps['time'] = $storedCrit['lp']['modifier'] == "hours" ?
                        intval(floatval($storedCrit['lp']['value']) * 60 * 60) :
                        intval($storedCrit['lp']['value'] * 60);
                $utcTimezone = new DateTimeZone("UTC");
                $LPDateTimeFrom = new DateTime("now", $utcTimezone);
                $LPDateTimeTo = new DateTime("now", $utcTimezone);
                $hours = $lps['time'] / 60 / 60;
                $LPDateTimeFrom->sub(new DateInterval('PT'.$hours.'H'));
                $LPDateTimeTo->add(new DateInterval('PT'.$hours.'H'));
                $LPDTFrom = $LPDateTimeFrom->format("Y-m-d H:i:s");
                $LPDTTo = $LPDateTimeTo->format("Y-m-d H:i:s");

                $lps['items'] = null;

                $qry->add("lptime", $LPDTFrom, "<="); //also check the lptime (last played) field because... why the hell not :)
                $qry->add("schedule.starts", $LPDTFrom, "<=");
                $qry->add("schedule.starts", $LPDTTo, ">=");
            }
    $qry->groupByDbId();

    Of course there are some drawbacks that i cant overcome that easy.. for example if you add a dynamic block to multiple shows at once then i am not 100% sure that this check will work between the shows that are scheduled at the same time.

    Anyway... I think that's it :) If you have the mood and the patience to try the above the it would be great so we can see if it works or not and how it works. If you can tweak it and make it better then that would be great! 
    Please if you try any of these (or do your own tests) do it in a development server and always keep a backup of the files you are messing with! after all i changed a lot of files and i cannot be 100% sure that i didnt forgot something i did and i should write here!

    Ps. if someone has posted a solution to this issue in the past then please tell me so.

    Best Regards from Greece.
  • and... ofcourse i forgot something....

    You must also add within the file /usr/share/airtime/application/views/scripts/form/smart-block-criteria.phtml the following code after <dd id='sp_limit-element'>...</dd>

    <dd id='sp_lp-element'>
                <span class='sp_text_font'><?php echo $this->element->getElement('sp_lp_value')->getLabel() ?></span>
                <?php echo $this->element->getElement('sp_lp_value')?>
                <?php echo $this->element->getElement('sp_lp_options') ?>
                <?php if($this->element->getElement("sp_lp_value")->hasErrors()) : ?>
                    <?php foreach($this->element->getElement("sp_lp_value")->getMessages() as $error): ?>
                    <span class='errors sp-errors'>
                       <?php echo $error; ?>
                    </span>
                    <?php endforeach; ?>
                <?php endif; ?>
            <br />
            </dd>


    Also within the file Block.php in function between if ($criteria == "limit") { and } else if($criteria == "repeat_tracks") { insert this code

    } else if ($criteria == "lp") {
                        $storedCrit["lp"] = array(
                        "value"=>$value,
                        "modifier"=>$modifier,
                        "display_modifier"=>_($modifier));
    Post edited by Stathis Simos at 2015-11-30 11:57:23
  • UPDATE FOR FUTURE Shows

    ***NOTE i dont use linked shows so i dont know if everything works well for this type of show! please do your checks in a development server and keep backup first!!! ***

    Instead of checking using "now", the correct approach is to check the start day of every instance of the show we are adding items to.

    To do this we need to somehow pass the start date of the instance to the getListofFilesMeetCriteria function inside the file Block.php we talked about.
    so first we change the function to getListofFilesMeetCriteria($DT)
    We also change the function getListOfFilesUnderLimit() to getListOfFilesUnderLimit($DT)
    Inside the function getListOfFilesUnderLimit we change the line:
    $info       = $this->getListofFilesMeetCriteria();
    to
    $info       = $this->getListofFilesMeetCriteria($DT);

    Inside the function getListofFilesMeetCriteria we will use the new parameter like this:
    We change the line $LPDateTimeFrom = new DateTime("now", $utcTimezone);
    to the following two lines:
    $LPDateTimeFrom = $DT;
    $LPDateTimeFrom = new DateTime($LPDateTimeFrom->format("Y-m-d H:i:s"), $utcTimezone);

    and delete (or comment) lines
    $LPDateTimeTo = new DateTime("now", $utcTimezone);
    and
    $LPDateTimeTo->add(new DateInterval('PT'.$hours.'H'));
    and
    $LPDTTo = $LPDateTimeTo->format("Y-m-d H:i:s");
    and
    $qry->and("schedule.starts", $LPDTTo, ">="); 


    Inside the function generateSmartBlock
    we change the line $insertList = $this->getListOfFilesUnderLimit();
    to the following two lines:
    $DT = new DateTime("now", $utcTimezone);
    $insertList = $this->getListOfFilesUnderLimit($DT);


    In the file SmartBlockCriteria.php (application/forms)

    Inside the startForm function
    Above the comment //getting block content candidate count that meets criteria put the two lines:
    $utcTimezone = new DateTimeZone("UTC");
    $DT = new DateTime("now", $utcTimezone);

    and then change the line:
    $files = $bl->getListofFilesMeetCriteria();
    to
    $files = $bl->getListofFilesMeetCriteria($DT);

    Finally in the file Scheduler.php (/usr/share/airtime/application/models/Scheduler.php)
    Change the function retrieveMediaFiles($id, $type) to retrieveMediaFiles($id, $type, $DT)
    and inside that function change the line $dynamicFiles = $bl->getListOfFilesUnderLimit();
    to 
    $dynamicFiles = $bl->getListOfFilesUnderLimit($DT);

    ** Note.. there are two lines, change both! **

    Inside function insertAfter
    Under 
    foreach($instances as &$instance) {
                        //reset
                        $this->applyCrossfades = true;
    add the line
    $showStartDT = new DateTime($instance["starts"], new DateTimeZone("UTC"));

    Finally change the line $this->retrieveMediaFiles($media["id"], $media["type"]));
    to
    $this->retrieveMediaFiles($media["id"], $media["type"], $showStartDT));

    Now if everything is OK then when you add dynamic content into a future show the check for each item to avoid rescheduling inside a specific time frame will now be donw using the show (instance) start date and not "now"


    Post edited by Stathis Simos at 2015-12-03 08:36:21
  • Hello,

    Very good work. Is that OK for you ?

    I would like to do the same thing but I am afraid to all these changes. (I am afraid to do mistakes :)

    Can you send me your pages with all changes please ?

    thank in advance
    Patrick
  • I think its ok for me till now BUT everything above is for testing only in order either to confirm that it works or change it together with the community to make it better, If you want a solution to a production server then you better stay away from this :)
    If you have a testing server OR if you are just playing around with airtime and can keep backup to every page you mess with then you can go ahead and try to change the pages i mention (version 2.5.1 of airtime as i said)
    In general if you mess up with something in any of those pages the only thing that wont work anymore is the specific page in the admin site and wont affect the playout or any other website (for example the website for your station) at all but that doesnt mean that you shouldnt always keep a backup because even a single wrong character can give you a blank page and there is no such thing like easy debugging :) (and also i cannot guarantee that i havent forgot anything because when i write and delete code for hours i usually make a mess until i finish.. and i am not finished)

    PS. I am experimenting a lot (i shouldnt do that in a live radio station i know...) so trust me you DONT want to have my pages ;)
    Post edited by Stathis Simos at 2015-12-11 20:39:12
  • Vote Up0Vote Down hoerichhoerich
    Posts: 627Member, Airtime Moderator
    good job
    Official Airtime Forum Manager
    --------------------------
    Most of the time an issue is located between keyboard and chair.
  • Hello,

    Very good idea and work. 

    But, there is a problem with... 
    $qry->add("lptime", $LPDTFrom, "<="); //also check the lptime (last played) field because... why the hell not 
    ...That directive don't allow "never played" titles to be selected.

    and 
     $qry->add("schedule.starts", $LPDTFrom, "<=");
     $qry->add("schedule.starts", $LPDTTo, ">=");
    ... don't allow never scheduled tracks to be selected.

    my knowledge of zend is very poor so i don't know how to correct that...

    Also, i don't understand the "element" choice of the "Check track Schedule" criteria. Where is the control in the code ?

    Regards,
    Gael
  • You are probably right (sorry for taking so long to see your comment but it's been over a month since i last logged in here.. some health issues in my family and i really dont have any time for any of this)

    The code should probably be like that

        $qry->add("lptime", $LPDTFrom, "<=");
                $qry->addOr("lptime", null, Criteria::ISNULL);
                $qry->add("schedule.starts", $LPDTFrom, "<=");
                $qry->addOr("schedule.starts", null, Criteria::ISNULL);


    I dont have the line "$qry->add("schedule.starts", $LPDTTo, ">=");" anywhere in my code and i dont remember why i deleted it :) Probably worked as good or better without it.

    Try it and tell me if everything is ok now
  • Also what do you mean about the ""element" choice of the "Check track Schedule" criteria" ?!?!?

    If you see the screenshot you will see this choise in Library (airtime admin), This element is sp_lp_value as you will see inside code.