import java.text.SimpleDateFormat;
import org.serviio.library.metadata.*
import org.serviio.library.online.*
import org.apache.commons.lang.*

/**
 * SvtPlay.se Web resource plugin. 
 * 
 * ##############################################
 * Resource URL instructions
 * ##############################################
 * 
 * This is a plugin of the type "Web Resource" ("Webbresurs").
 * 
 * There is a separate plugin for RSS feeds, which may be prefered as it gives accurate 
 * information about the publishing date of each individual episode.
 * 
 * Find URLs for categories by navigating to your desired category at the top of http://www.svtplay.se/program
 * Right click on your desired category's thumb nail image and copy the shortcut to the clipboard. For documentaries
 * ths URL is http://www.svtplay.se/dokumentar. By default this will give you *the latest* episiodes
 * for your selected show.
 *
 * You can also add individual shows, links to which are also found at:
 * http://www.svtplay.se/program
 * For example, the show "Fotbollskväll" has the feed URL
 * http://www.svtplay.se/fotbollskvall
 *
 * As of version 17, the plugin understands the tab=<tabname> parameters at the end of the URL:s.
 * So, if you navigate to a show (or category) and then press a button for "Populäraste", "Senaste",
 * "Sista chansen" or "Klipp", you will get the episodes by that filter if you copy the resulting 
 * URL from the address field of your web browser. 
 * 
 * Example. To get the latest shows of "Fotbollskväll", register the URL:
 * http://www.svtplay.se/fotbollskvall
 *
 * If you want one feed to display ONLY the shows and another feed to display ONLY the clips,
 * use these two feed urls:
 * 1) http://www.svtplay.se/fotbollskvall
 * 2) http://www.svtplay.se/fotbollskvall?tab=klipp
 *
 * If you want a feed with the latest shows AS WELL as the clips, you will need to register a special URL:
 * http://www.svtplay.se/fotbollskvall?serviioclips=1
 * 
 * For the most popular children's shows, register the URL:
 * http://www.svtplay.se/barn?tab=populara
 *
 * For film and drama shows which will soon become unavailable, register:
 * http://www.svtplay.se/filmochdrama?tab=sista-chansen
 * 
 * Live feeds are included since version 1.1.6.
 * They can sometimes be included in certain category pages (such as news).
 * Find live feeds here:
 * http://www.svtplay.se/kanaler
 * 
 * ##############################################
 * Version history
 * ##############################################
 *
 * @version 22 
 *
 * Fixed in version 22: Accomodate for altered markup when extracting video json url.
 *                      Better support for various m3u8 formats in SvtPlayCommon.
 *
 * Fixed in version 21: Bug fix to handle altered markup for playLink css class renamed again
 *                      Extended support for new type of listings such as the Agenda program
 *
 * Fixed in version 20: Bug fix to handle altered markup for link to JSON file containing URL to 
 *                      to stream manifest. Removed most of the not-so-interesting version history.
 *
 * Fixed in version 19: Bug fix to handle altered markup for playLink css class renamed again
 *
 *
 * ##############################################
 * Credits
 * ############################################## 
 *
 * @author Otto Dandenell 
 * 
 * The plugin re-uses some of the extraction methods from the original SvtPlay implementation by Tomas Falemo
 *
 */



class SvtPlay extends org.serviio.library.online.WebResourceUrlExtractor {
    
    final boolean debug = false;

    SvtPlayCommon svtPlayCommon = new SvtPlayCommon(debug)

    final VALID_RESOURCE_URL = '^http(?:s)?://www.svtplay.se/.*$'
    final RSS_RESOURCE_URL = '^http(?:s)?://www.svtplay.se/.*/rss.xml$'

    final int VERSION = 22;
        
    private static SimpleDateFormat dfHourMinute = new SimpleDateFormat( "HH:mm" )

    static main(args) {
        
        // this is just to test the SvtPlay plugin            
//        def TestUrlHttp = new URL("http://www.svtplay.se/fotbollskvall")
//        def TestUrlHttp = new URL("http://www.svtplay.se/fotbollskvall?serviioclips=1")
//        def TestUrlHttp = new URL("http://www.svtplay.se/barn?tab=senaste")
//        def TestUrlHttp = new URL("http://www.svtplay.se/barn?tab=populara")
//        def TestUrlHttp = new URL("http://www.svtplay.se/barn?tab=sista-chansen")           
//        def TestUrlHttp = new URL("http://www.svtplay.se/abc")
        def TestUrlHttp = new URL("http://www.svtplay.se/agenda")
//        def TestUrlHttp = new URL("http://www.svtplay.se/alex-hittar-hobbyn")
//        def TestUrlHttp = new URL("http://www.svtplay.se/dokumentar")        
//        def TestUrlHttp = new URL("http://www.svtplay.se/nyheter")
//        def TestUrlHttp = new URL("http://www.svtplay.se/nyheter?tab=regionala")
//        def TestUrlHttp = new URL("http://www.svtplay.se/nyheter?serviioclips=1&serviioepisodes=0")
//        def TestUrlHttp = new URL("http://www.svtplay.se/nyheter?tab=klipp")
//        def TestUrlHttp = new URL("http://www.svtplay.se/palme")
//        def TestUrlHttp = new URL("http://www.svtplay.se/barn")
        

        def TestUrlLive = new URL("http://www.svtplay.se/kanaler")
        def TestUrlRtmp = new URL("http://www.svtplay.se/homeland")
        def TestUrlRss = new URL("http://www.svtplay.se/dokumentar/rss.xml")
        def TestUrlExternal = new URL("http://google.com/feeds/api/standardfeeds/top_rated?time=today") 

        SvtPlay extractor = new SvtPlay()
        
        println "PluginName                  : " + extractor.getExtractorName();
        println "Plugin version              : " + extractor.getVersion();
        println "TestMatch HTTP              : " + extractor.extractorMatches(TestUrlHttp);
        println "TestMatch Live              : " + extractor.extractorMatches(TestUrlLive);
        println "TestMatch Rtmp              : " + extractor.extractorMatches(TestUrlRtmp);
        println "TestMatch Rss               : " + extractor.extractorMatches(TestUrlRss);
        println "TestMatch external (Google) : " + extractor.extractorMatches(TestUrlExternal);
    
        println ''
        
        assert extractor.extractorMatches( TestUrlHttp )
        assert extractor.extractorMatches( TestUrlLive )
        assert extractor.extractorMatches( TestUrlRtmp )
        assert !extractor.extractorMatches( TestUrlRss )
        assert !extractor.extractorMatches( TestUrlExternal )
        
        WebResourceContainer containerLive = extractor.extractItems(TestUrlLive, 24);

        ContentURLContainer result1Live = null
        ContentURLContainer result2Live = null
        ContentURLContainer result3Live = null
        if (containerLive.getItems().size() > 0)
        {
            println ' containerLive.getItems()[0].releaseDate: ' + containerLive.getItems()[0].releaseDate
            result1Live = extractor.extractUrl(containerLive.getItems()[0], PreferredQuality.LOW)

            for (int index = 0; index < containerLive.getItems().size(); index++)
            {
                println 'http item #' + index + ': ' + containerLive.getItems()[index].title + ', videoArticleUrl: ' + containerLive.getItems()[index].getAdditionalInfo()['videoArticleUrl']
            }
            result2Live = extractor.extractUrl(containerLive.getItems()[1], PreferredQuality.MEDIUM)
            result3Live = extractor.extractUrl(containerLive.getItems()[2], PreferredQuality.HIGH)
        }
//        ContentURLContainer result1Live = extractor.extractUrl(containerLive.getItems()[0], PreferredQuality.LOW)
        println "Result low Live: $result1Live"
        println "Result medium Live: $result2Live"
        println "Result high Live: $result3Live"
        
        println ''
        println 'Calling extractor.extractItems on TestUrlHttp: ' + TestUrlHttp

        WebResourceContainer containerHttp = extractor.extractItems(TestUrlHttp, 4);
        println 'containerHttp.getItems().size(): ' + containerHttp.getItems().size()

        ContentURLContainer result1Http = null
         if (containerHttp.getItems().size() > 0)
         {
            println ' containerHttp.getItems()[0].releaseDate: ' + containerHttp.getItems()[0].releaseDate
            result1Http = extractor.extractUrl(containerHttp.getItems()[0], PreferredQuality.LOW)

            for (int index = 0; index < containerHttp.getItems().size(); index++)
            {
                println 'http item #' + index + ': ' + containerHttp.getItems()[index].title + ', videoArticleUrl: ' + containerHttp.getItems()[index].getAdditionalInfo()['videoArticleUrl']
            }
         }
//        ContentURLContainer result1Http = extractor.extractUrl(containerHttp.getItems()[0], PreferredQuality.LOW)
        ContentURLContainer result2Http = extractor.extractUrl(containerHttp.getItems()[1], PreferredQuality.MEDIUM)
       // ContentURLContainer result3Http = extractor.extractUrl(containerHttp.getItems()[3], PreferredQuality.HIGH)
        println "Result low HTTP: $result1Http"
        println "Result medium HTTP: $result2Http"
       // println "Result high HTTP: $result3Http"

        println ''

        WebResourceContainer containerRtmp = extractor.extractItems(TestUrlRtmp, 3)

        if (containerRtmp.getItems().size() > 0)
        {
            ContentURLContainer result1Rtmp = extractor.extractUrl(containerRtmp.getItems()[0], PreferredQuality.LOW)
            println "Result low Rtmp: $result1Rtmp"
            ContentURLContainer resultHighRtmp = extractor.extractUrl(containerRtmp.getItems()[0], PreferredQuality.HIGH)
            println "Result high Rtmp: $resultHighRtmp"

        }
        else
        {
            println 'No qbrick items found!'
        }
        if (containerRtmp.getItems().size() > 1)
        {
            ContentURLContainer result2Rtmp = extractor.extractUrl(containerRtmp.getItems()[1], PreferredQuality.MEDIUM)
            println "Result medium Rtmp: $result2Rtmp"
        }
        if (containerRtmp.getItems().size() > 2)
        {
            ContentURLContainer result3Rtmp = extractor.extractUrl(containerRtmp.getItems()[2], PreferredQuality.HIGH)
            println "Result high Rtmp: $result3Rtmp"
        }
    }


    String getExtractorName() {
        return getClass().getName()
    }

    int getVersion() {
        return VERSION
    }
    
    boolean extractorMatches(URL resourceUrl) {
        return (resourceUrl ==~ VALID_RESOURCE_URL) && !(resourceUrl ==~ '^.*.xml$')
    }

    /* Parse the name of the target tab */
    String getSelectedOrDefaultTabName(URL resourceUrl, String resourceHtml) {
        
        def tabLinkLabel = ""
        def matcherTabLinkLabel = (resourceUrl =~ '.*?(?:[?|&]tab=)([^&$]*)')
        if (matcherTabLinkLabel != null && matcherTabLinkLabel.size() > 0)
        {
            tabLinkLabel = matcherTabLinkLabel[0][1]
        }
        
        if (!tabLinkLabel.equals(""))
        {
            // Find the matching link and extract the target
            
            // <a href="/barn?tab=sista-chansen#play_navigation-tabs"
            //     id="popular-videos-tab"
            
            // <a href="?tab=klipp" id="more-clips-tab" class="play_button play_js-tab is--inactive"
            
            def tabLabelMatcher = (resourceHtml =~ '(?s)<a [^>]*?[?|&]tab=' + tabLinkLabel + '[#&"][^>]* id=\"([^"]*?)\"')
            if (tabLabelMatcher != null && tabLabelMatcher.size() > 0)
            {
                return tabLabelMatcher[0][1]
            }
        }
        
        // If we reached this far, look for the first label link which is NOT A - Ö
        def tabLabelMatcherAll = (resourceHtml =~ '(?s)<a [^>]*?[?|&]tab=(?:[^#&"]*?)[#&"][^>]* id=\"([^"]*?)\"')
        if (tabLabelMatcherAll != null && tabLabelMatcherAll.size() > 0)
        {
            def firstLabel = ""
            for (int tabLabelIndex = 0; tabLabelIndex < tabLabelMatcherAll.size(); tabLabelIndex++)
            {
                if (debug)
                {
                    println 'tabLabelIndex: ' + tabLabelIndex
                    println 'tabLabelMatcherAll[tabLabelIndex][1]: ' + tabLabelMatcherAll[tabLabelIndex][1]
                }
                if (!tabLabelMatcherAll[tabLabelIndex][1].equals("alphabetic-list-tab")) 
                {
                    // We prioritize labels which start with "more" or "latest"
                    if (tabLabelMatcherAll[tabLabelIndex][1].startsWith("latest-"))
                        return tabLabelMatcherAll[tabLabelIndex][1];
                    if (tabLabelMatcherAll[tabLabelIndex][1].startsWith("more-"))
                        return tabLabelMatcherAll[tabLabelIndex][1];

                    // Otherwise, remember it if it's the first one
                    if (firstLabel.equals("")) firstLabel = tabLabelMatcherAll[tabLabelIndex][1]                        
                }
            }
            
            // If we have reached this far, return the first label we found.
            if (!firstLabel.equals("")) return firstLabel
        }
        
        // default
        return ""
    }
    
    WebResourceContainer extractItems(URL resourceUrl, int maxItemsToRetrieve) {
        log( getExtractorName() + ': extracting items for url: ' +  resourceUrl + ' , maxItemsToRetrieve: ' + maxItemsToRetrieve)
        def resourceHtml
        
        /* Find out if the resourceUrl is configured to fetch clips or shows or both. */
        def matcherServiioClips = (resourceUrl =~ '.*(?:[?|&]serviioclips=)([^&$]*)')
        def matcherServiioEpisodes = (resourceUrl =~ '.*(?:[?|&]serviioepisodes=)([^&$]*)')
        
        def blnIncludeServiioClips = false
        def blnIncludeServiioEpisodes = true
        def blnIncludeServiioLive = true
        
        if (matcherServiioClips != null && matcherServiioClips.size() > 0)
        {
            switch (matcherServiioClips[0][1])
            {
                case "1":
                case "true":
                case "yes":
                    blnIncludeServiioClips = true
                    break
                default:
                    blnIncludeServiioClips = false
            }
        }
        if (matcherServiioEpisodes != null && matcherServiioEpisodes.size() > 0)
        {
            switch (matcherServiioEpisodes[0][1])
            {
                case "1":
                case "true":
                case "yes":
                    blnIncludeServiioEpisodes = true
                    break
                default:
                    blnIncludeServiioEpisodes = false
            }
        }
        
        /* Remove serviio request parameters */
        def properResourceUrl = (resourceUrl =~ '(?:serviioclips=[^&]*&?)').replaceAll('')
        properResourceUrl = (properResourceUrl =~ '(?:serviioepisodes=[^&]*&?)').replaceAll('')
        
        
        /* Remove trailing ampersands and question marks. */
        while (properResourceUrl ==~ /.*[\?&]$/)
        {
            properResourceUrl = (properResourceUrl =~ /[\?&](?:$)/).replaceAll('')
        }
                
        def url = new URL(''+properResourceUrl)
        
        
        def connection = url.openConnection()
        if(connection.responseCode == 200){
            resourceHtml = connection.content.text
        }
        else{
            return null
        }
                
        def selectedTabName = getSelectedOrDefaultTabName(resourceUrl, resourceHtml)
        //if (debug) println 'selectedTabName: ' + selectedTabName
        
        // find out the title
        def title
        //<title>Barn | SVT Play</title>
        def matcherTitle = (resourceHtml =~ '<title>(.*?)</title>')
        if(matcherTitle.size() > 0)
        {
            title = StringEscapeUtils.unescapeHtml(matcherTitle[0][1])
            if (debug) println 'title - ' + title
        }
        
        // Find a category thumbnail by matching known categories
        def thumbnailUrl
        if (resourceUrl ==~ '.*/barn.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/categories/posters/barn.png"
        }
        else if (resourceUrl ==~ '.*/dokumentar.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/categories/posters/dokumentar.png"
        }
        else if (resourceUrl ==~ '.*/filmochdrama.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/categories/posters/filmochdrama.png"
        }
        else if (resourceUrl ==~ '.*/kulturochnoje.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/categories/posters/kulturochnoje.png"
        }
        else if (resourceUrl ==~ '.*/nyheter.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/categories/posters/nyheter.png"
        }
        else if (resourceUrl ==~ '.*/samhalleochfakta.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/categories/posters/samhalleochfakta.png"
        }
        else if (resourceUrl ==~ '.*/sport.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/categories/posters/sport.png"
        }
        else if (resourceUrl ==~ '.*/os.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/os.png"
        }
        else if (resourceUrl ==~ '.*/oppetarkiv.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/categories/posters/oppetarkiv.png"
        }
        else if (resourceUrl ==~ '.*/live.*$')
        {
            thumbnailUrl = "http://www.svtplay.se/public/images/live.png"
        }
        else
        {
            // All programs. 
            //<meta property="og:image" content="/public/images/play_default_large.jpg"/>
            def matcherLogo = (resourceHtml =~ '(?s)<meta property="og\\:image" content="([^"]*?)"')
            if(matcherLogo.size() > 0)
            {
                thumbnailUrl = matcherLogo[0][1]
                if (!thumbnailUrl.contains("http://")) thumbnailUrl = "http://www.svtplay.se" + thumbnailUrl
                
                // Thumnail url:s may contain HTML escape sequences, which must be unescaped before being fed to Serviio.
                if (thumbnailUrl != null)
                    thumbnailUrl = org.apache.commons.lang.StringEscapeUtils.unescapeHtml(thumbnailUrl)
            }
        }
        if (debug) println 'thumbnailUrl - ' + thumbnailUrl
        
        List<WebResourceItem> items = [] 
        // Parse items from markup, return only max items
        /*
              <article class="svtUnit svtNth-4 svtMediaBlock playPositionRelative playJsInfo-Core playJsInfo-Hover playIEFixedHeight"
                 data-title="ABC - 9/11 18:10"
                 data-description=""
                 data-length="5 min"
                 data-available="5 dagar kvar"
                 data-broadcasted="fre 9 nov"
                 data-published=""
                 data-broadcastStartTime="">
                        <div class="playDisplayTable">
                <a href="/video/574063/9-11-18-10" class="playLink playBoxWithClickArea playIELinkFix">
                    <div>
                        <div class="svtMBFig-L-O-O-O svtMediaBlockFig-L playJsInfo-ImageContainer playPositionRelative">
                            <img class="playGridThumbnail" alt="ABC - 9/11 18:10" src="http://www.svt.se/cachable_image/1352482140000/svts/article574062.svt/ALTERNATES/medium/default_title"/>
                                                    </div>
                        <span class="playDisplayTouch-N-I-I-I playJsInfo-Open playIconInline playIcon-ArrowDown playFloatRight"></span>
                        <h5 class="playGridHeadline">
                                                            9/11 18:10
                                                                                </h5>
                                                    <p class="svtXColorWhite">Sändes:
                                <time datetime="2012-11-09T18:10+01:00">
                                fre 9 nov
                                </time>
                            </p>
                                                                    </div>
                </a>
                <a href="?" class="playClickArea playJsInfo-Open">
                    <span class="playIconInline playIcon-ArrowDown svtHide-Gte-S"></span>
                </a>
            </div>
        </article>        
        */
        
        // Episodes or clips or both? They are encapsulated by divs with the data-tabname attribute.
        def strServiioEpisodesHTML = ""
        if (blnIncludeServiioEpisodes)
        {
            // Include episodes
            //def episodesMatcher =  resourceHtml =~ '(?s)<div [^>]*data-tabname="(?:senasteprogram|program|episodes)"[^>]*?>.*?(?:<div [^>]*data-tabname=|</body>)'
            def episodesMatcher 
            if (!selectedTabName.equals(""))
            {
                if (debug) println 'Matching on selectedTabName = ' + selectedTabName
                episodesMatcher =  resourceHtml =~ '(?s)<div [^>]*(?:labelledby)="' + selectedTabName + '"[^>]*?>.*?(?:<div [^>]*(?:labelledby)=|</body>)'
            }
            else
            {
                episodesMatcher =  resourceHtml =~ '(?s)<div [^>]*(?:labelledby)="(?:latest-videos-tab|more-episodes-tab|more-episodes-panel)"[^>]*?>.*?(?:<div [^>]*(?:labelledby)=|</body>)'
            }
            if (episodesMatcher != null && episodesMatcher.getCount() > 0)
            {
                strServiioEpisodesHTML = episodesMatcher[0]
            }
        }
        //if (debug) println 'strServiioEpisodesHTML: ' + strServiioEpisodesHTML
        
        def strServiioClipsHTML = ""
        if (blnIncludeServiioClips)
        {
            // Include episodes
            def clipsMatcher =  resourceHtml =~ '(?s)<div [^>]*(?:labelledby)="(?:latest-clips-tab|more-clips-tab)"[^>]*?>.*?(?:<div [^>]*(?:labelledby)=|</body>)'
            if (clipsMatcher != null && clipsMatcher.getCount() > 0)
            {
                strServiioClipsHTML = clipsMatcher[0]
            }
        }
        //if (debug) println 'strServiioClipsHTML: ' + strServiioClipsHTML

        def strServiioLiveHTML = ""
        if (blnIncludeServiioLive)
        {
            // Include episodes
            def liveMatcher =  resourceHtml =~ '(?s)<div [^>]*data-tabname=\"live\"[^>]*?>.*?(?:<div [^>]*data-tabname=|</body>)'
            if (liveMatcher != null && liveMatcher.getCount() > 0)
            {
                strServiioLiveHTML = liveMatcher[0]
            }
            
            //if (debug) println 'strServiioLiveHTML: ' + strServiioLiveHTML         
        }
        
        def itemsAdded = 0;

        // We put live feeds at the top, clips at the bottom.
        // Live feeds have two special patterns.
        if (blnIncludeServiioLive)
        {
            // Hashmap to avoid including the same channel / show twice
            HashMap liveMap = new HashMap()
            
            
            // Channels at http://www.svtplay.se/kanaler
            def liveKanalerContainerMatcher = resourceHtml =~'(?s)<ul [^>]*data-player=\"player\"(.*?)</ul>'
            if (liveKanalerContainerMatcher != null && liveKanalerContainerMatcher.getCount() > 0)
            {
                
                def liveKanalerMatcher = liveKanalerContainerMatcher[0][1] =~ '(?s)<a [^>]*href=\"(/kanaler/[^\"]*)\"[^>]*>(.*?)</a>'    
                //println 'liveKanalerMatcher.getCount(): ' + liveKanalerMatcher.getCount()
                if (liveKanalerMatcher != null && liveKanalerMatcher.getCount() > 0)
                {
                    if (debug) println 'liveKanalerMatcher.getCount(): ' + liveKanalerMatcher.getCount()
                    for (int i = 0; i < liveKanalerMatcher.getCount(); i++)
                    {
                        def strKanalPath = liveKanalerMatcher[i][1]
                        strKanalPath = (strKanalPath =~ '([?]type=embed$)').replaceAll('')
                        if (debug) println i + ': ' + strKanalPath
                        
                        if (!liveMap.containsKey(strKanalPath))
                        {
                            def videoArticleUrl = 'http://www.svtplay.se' + strKanalPath
                            if (debug) println 'videoArticleUrl ' + i + ': ' + videoArticleUrl
    
                            def strKanalHtml = liveKanalerMatcher[i]
                            /*
<a href="/kanaler/svt1" class="svtJsTab playJsShareTab playJsZapper-Channel" data-tab="1" data-thumbnail="/public/images/channels/backgrounds/svt1-background.jpg" data-jsonhref="/kanaler/svt1" data-channel="svt1" title="SVT1 | SVT Play">
                        <div class="playChannelLogoContainer">
                            <img class="svtHide-Lte-S playChannelMenuLogo" alt="SVT1" src="/public/images/channels/svt1.png" />
                            <img class="svtHide-Gte-M playChannelMenuLogo" alt="SVT1" src="/public/images/channels/svt1-small_2x.png" />
                        </div>
                        <div class="svtHide-Lte-S playChannelMenuDashboard playJsSchedule-Active" data-channeldashboard="svt1">
                            <span class="playChannelMenuTitle">Cimarron strip: Sound of a drum</span>
                            <div class="playMenuProgressbar playJsSchedule-ProgressBar" role="progressbar" aria-valuetext="11%" aria-valuemin="0" aria-valuemax="100" aria-valuenow="11" >
    <div class="playMenuProgressbarValue playJsSchedule-Progress"
             style="width:11%;"
             data-programpercent="11"
             data-servertime="1360158821394"
             data-starttime="1360158300000"
             data-endtime="1360162800000">
    </div>
</div>

                        </div>
                    </a>                            
                            */
                            // Title
                            def liveTitle = "";
                            def liveTitleMatcher = strKanalHtml =~ '<span class=\"playChannelMenuTitle\">(.*?)</span>'
                            if (liveTitleMatcher != null && liveTitleMatcher.getCount() > 0)
                                liveTitle = org.apache.commons.lang.StringEscapeUtils.unescapeHtml(liveTitleMatcher[0][1])
                            if (debug) println 'Title ' + i + ': ' + liveTitle
                            
                            // Channel name
                            def channelName = ""
                            def channelLogo
                            def liveChannelLogoMatcher = strKanalHtml =~ '<img class=\"[^\"]*playChannelMenuLogo[^\"]*\"[^>"]*?alt="([^\"]*)"[^>"]*?src="([^\"]*)"'
                            if (liveChannelLogoMatcher != null && liveChannelLogoMatcher.getCount() > 0)
                            {
                                channelName = org.apache.commons.lang.StringEscapeUtils.unescapeHtml(liveChannelLogoMatcher[0][1])
                                channelLogo = liveChannelLogoMatcher[0][2]
                            }
                            if (debug) println 'channelName ' + i + ': ' + channelName
                            if (debug) println 'channelLogo ' + i + ': ' + channelLogo
                                                        
                            // Thumbnail
                            def videoArticleThumbnailUrl
                            def videoArticleThumbnailUrlMatcher = strKanalHtml =~ '(?s)<a [^>]*href=\"/kanaler/[^\"]*\"[^>]*?data-thumbnail=\"([^\"]*)\"'    
                            if (videoArticleThumbnailUrlMatcher != null && videoArticleThumbnailUrlMatcher.getCount() > 0)
                            {
                                videoArticleThumbnailUrl = videoArticleThumbnailUrlMatcher[0][1]

                                // Thumnail url:s may contain HTML escape sequences, which must be unescaped before being fed to Serviio.
                                if (videoArticleThumbnailUrl != null)
                                    videoArticleThumbnailUrl = org.apache.commons.lang.StringEscapeUtils.unescapeHtml(videoArticleThumbnailUrl)
                            }
                            if (debug) println 'videoArticleThumbnailUrl ' + i + ': ' + videoArticleThumbnailUrl
                            
                            // Starttime + endtime
                            /*
                             data-starttime="1360158300000"
                             data-endtime="1360162800000">
                            */
                            Date startTime = new Date()
                            Date endTime = new Date()
                            def startTimeMatcher = strKanalHtml =~ '(?s)<div class=\"playMenuProgressbarValue[^>]*?data-starttime=\"([^\"]*)\"[^>]*?data-endtime=\"([^\"]*)\"'
                            def startTimeMillisecs = ""
                            def endTimeMillisecs = ""
                            if (startTimeMatcher != null && startTimeMatcher.getCount() > 0)
                            {
                                startTimeMillisecs = startTimeMatcher[0][1]
                                endTimeMillisecs = startTimeMatcher[0][2]
                                //println 'startTimeMillisecs ' + i + ': ' + startTimeMillisecs
                                //println 'endTimeMillisecs ' + i + ': ' + endTimeMillisecs                                                                
                                try
                                {
                                    startTime.setTime(Long.parseLong(startTimeMillisecs, 10))
                                    //println 'startTime ' + i + ': ' + startTime
                                }
                                catch(Exception ex)
                                {
                                     log('ERROR:  Could not parse start time of live show (startTimeMillisecs: ' + startTimeMillisecs + '): ' + ex.getMessage())
                                     if (debug) println 'ERROR:  Could not parse start time of live show (startTimeMillisecs: ' + startTimeMillisecs + '): ' + ex.getMessage()
                                }
                                try
                                {
                                    endTime.setTime(Long.parseLong(endTimeMillisecs, 10))
                                    if (debug) println 'endTime ' + i + ': ' + endTime
                                }
                                catch(Exception ex)
                                {
                                     log('ERROR:  Could not parse end time of live show (endTimeMillisecs: ' + endTimeMillisecs + '): ' + ex.getMessage())
                                     if (debug) println 'ERROR:  Could not parse start time of live show (endTimeMillisecs: ' + endTimeMillisecs + '): ' + ex.getMessage()
                                }                               
                            }
                            
                            liveTitle = dfHourMinute.format(startTime) + ' - ' + dfHourMinute.format(endTime) + ': ' + liveTitle
                            if (channelName != null && !channelName.equals(""))
                            {
                                liveTitle = channelName + ' ' + liveTitle
                            }
                                                        
                            WebResourceItem item = new WebResourceItem(title: liveTitle, additionalInfo: ['videoArticleUrl': videoArticleUrl, 'videoArticleThumbnailUrl': videoArticleThumbnailUrl, 'live': 'true'])
                            item.releaseDate = startTime                        
                            items << item
                            itemsAdded++             
                        }
                    }
                }
            }
            if (!strServiioLiveHTML.equals(""))
            {            
                def liveArticleMatches = strServiioLiveHTML =~ '(?s)<a href=\"([^\"]*?)\"[^>]*>[^<>]*<article[^>]*?>.*?</article>[^<]*</a>'
                for(int i = 0; i < liveArticleMatches.getCount() && (maxItemsToRetrieve == -1 || itemsAdded < maxItemsToRetrieve) ; i++ ) {
                    def strLiveArticlePath = liveArticleMatches[i][1]
                    strLiveArticlePath = (strLiveArticlePath =~ '([?]type=embed$)').replaceAll('')
                    
                    if (!liveMap.containsKey(strLiveArticlePath))
                    {
                        // Flag will be set to true if we can determine that this show is live NOW.
                        def isBroadcastLive = false
                        def willBroadcastLiveLater = false
        
                        /*
                        Broadcast dates
                        <span class="playBroadcastTime">
                            <time datetime="2013-01-28T13:49+01:00">13.49</time> -
                            <time datetime="2013-01-28T17:00+01:00">17.00</time>
                        </span>
        
                        */
                        def broadcastTimeMatcher = liveArticleMatches[i] =~ '(?s)<[^>]*?class=\"playBroadcastTime[ \"]*?[^>]*>.*?<time datetime=\"([^\"]*?)\">.*?<time datetime=\"([^\"]*?)\">.*?(?:</span>|</div)>'
        
                        def startTime
                        def endTime
                        
                        if (broadcastTimeMatcher != null && broadcastTimeMatcher.getCount() > 0)
                        {
                            def strStartTime = broadcastTimeMatcher[0][1]
                        
                            try
                            {
                                startTime = svtPlayCommon.GetValidDate(strStartTime)
                            }
                            catch(Exception ex)
                            {
                                 log('ERROR:  Could not parse start time of live show (strStartTime: ' + strStartTime + '): ' + ex.getMessage())
                                 if (debug) println 'ERROR:  Could not parse start time of live show (strStartTime: ' + strStartTime + '): ' + ex.getMessage()
                            }
        
                            def strEndTime = broadcastTimeMatcher[0][2]
                            try
                            {
                                endTime = svtPlayCommon.GetValidDate(strEndTime)
                            }
                            catch(Exception ex)
                            {
                                 log('ERROR:  Could not parse end time of live show (strEndTime: ' + strEndTime + '): ' + ex.getMessage())
                                 if (debug) println 'ERROR:  Could not parse end time of live show (strEndTime: ' + strEndTime + '): ' + ex.getMessage()
                            }
                            Date now = new Date()
                            
                            if (now.after(startTime) && now.before(endTime)) {
                                isBroadcastLive = true
                            }
                            else if (now.before(startTime)) {
                                willBroadcastLiveLater = true
                            }
                        }
                        if (debug) println 'startTime : ' + startTime
                        if (debug) println 'endTime : ' + endTime
                        
                        
                        if (!isBroadcastLive)
                        {
                            // Try a different matcher
                            isBroadcastLive = strServiioLiveHTML ==~ '<img class=\"playBroadcastLiveIcon\"'                    
                        }
                        
                        if (isBroadcastLive || willBroadcastLiveLater)
                        {
                            if (debug) println 'FOUND ONGOING LIVE SHOW AT INDEX ' + i
        
                            def videoArticleUrl = 'http://www.svtplay.se' + strLiveArticlePath
                            if (debug) println '    LIVE videoArticleUrl: ' + videoArticleUrl
                            def liveTitle = ""
                            /* <h5>SVT Forum - 28/1 09.00</h5> */
                            if (willBroadcastLiveLater) {
                                /* <div class="playBroadcastTitle">Sverige idag - 28/1 17.30</div> */
                                def titleMatcher = liveArticleMatches[i] =~ '<div class=\"playBroadcastTitle\">(.*?)</div>'
                                if (titleMatcher != null && titleMatcher.getCount() > 0) {
                                    liveTitle = org.apache.commons.lang.StringEscapeUtils.unescapeHtml(titleMatcher[0][1])
                                }
                            }
                            else if (isBroadcastLive) {
                                def titleMatcher = liveArticleMatches[i] =~ '<h5>(.*?)</h5>'
                                if (titleMatcher != null && titleMatcher.getCount() > 0) {
                                    liveTitle = org.apache.commons.lang.StringEscapeUtils.unescapeHtml(titleMatcher[0][1])
                                }
                            }
                            if (debug) println 'liveTitle: ' + liveTitle
                            
                            // Try parsing the thumbnail
                            //<img  class="svtMediaBlockFig-L playBroadcastThumbnail" alt="SVT Forum - 28/1 09.00" src="http://www.svt.se/cachable_image/1338457984000/svts/article105942.svt/ALTERNATES/small/2112215-T.jpg"/>
                            def videoArticleThumbnailUrl = ""
                            def thumbnailMatcher = liveArticleMatches[i] =~ '(?s)<img[ ]*class=\"[^\"]*?playBroadcastThumbnail[^\"]*?\"[^>]*?src=\"([^\"]*?)\"'
                            if (thumbnailMatcher != null && thumbnailMatcher.getCount() > 0)
                            {
                                videoArticleThumbnailUrl = thumbnailMatcher[0][1]

                                // Thumnail url:s may contain HTML escape sequences, which must be unescaped before being fed to Serviio.
                                if (videoArticleThumbnailUrl != null)
                                    videoArticleThumbnailUrl = org.apache.commons.lang.StringEscapeUtils.unescapeHtml(videoArticleThumbnailUrl)
                                
                                log(' videoArticleThumbnailUrl: ' + videoArticleThumbnailUrl)
                            }
                            
                            WebResourceItem item = new WebResourceItem(title: liveTitle, additionalInfo: ['videoArticleUrl': videoArticleUrl, 'videoArticleThumbnailUrl': videoArticleThumbnailUrl, 'live': 'true'])
                            item.releaseDate = startTime                        
                            items << item
                            itemsAdded++             
                             
                        }
                
                        if (debug) println 'liveArticleMatches['+ i+ '] isBroadcastLive: ' + isBroadcastLive
                    }
                }
            }
        }
        // We put live feeds at the top, clips at the bottom.
        def strNonLiveFeedHTML = strServiioEpisodesHTML + strServiioClipsHTML    
        
        if (debug) println 'strNonLiveFeedHTML: ' + strNonLiveFeedHTML
        
        def articleMatches = strNonLiveFeedHTML =~ '(?s)<article [^>]*?>.*?</article>'

        // jämför om maxItemsToRetrieve är större än 8 och articleMatches.getCount() är lika med 8. Hämta isåfall nya artikellistor via ajax. 
        if (articleMatches.getCount() > 0 && (maxItemsToRetrieve == -1 || maxItemsToRetrieve > articleMatches.getCount()))
        {
            def antal = maxItemsToRetrieve
            if (antal == -1)
                antal = 100
                
            // Episodes
            if (blnIncludeServiioEpisodes)
            {
                if (debug) println 'Försöker hämta fler episoder via ajaxmönster...'
                def ajaxEpisodesMatcher =  strServiioEpisodesHTML =~ '(?s)data-baseurl=\"([^\"]*?)\".*?class=\"[^\"]*playShowMoreButton[^\"]*\"'
                if (ajaxEpisodesMatcher != null && ajaxEpisodesMatcher.getCount() > 0)
                {
                    def ajaxBaseUrl = ajaxEpisodesMatcher[0][1]
                    if (debug) println 'ajaxBaseUrl: ' + ajaxBaseUrl
                    def strAjaxEpisodesUrl = 'http://www.svtplay.se' + ajaxBaseUrl.replace("&amp;", "&") + 'sida=1&antal=' + antal
                    if (debug) println 'strAjaxEpisodesUrl: ' + strAjaxEpisodesUrl

                    def ajaxEpisodesUrl = new URL(''+strAjaxEpisodesUrl)
                                        
                    def ajaxEpisodesConnection = ajaxEpisodesUrl.openConnection()
                    if(ajaxEpisodesConnection.responseCode == 200){
                        strServiioEpisodesHTML = ajaxEpisodesConnection.content.text
                    }                                
                }
            }
            
            // Clips
            if (blnIncludeServiioClips)
            {
                if (debug) println 'Försöker hämta fler klipp via ajaxmönster...'
                def ajaxClipsMatcher =  strServiioClipsHTML =~ '(?s)data-baseurl=\"([^\"]*?)\".*?class=\"[^\"]*playShowMoreButton[^\"]*\"'
                if (ajaxClipsMatcher != null && ajaxClipsMatcher.getCount() > 0)
                {
                    def ajaxBaseUrlClips = ajaxClipsMatcher[0][1]
                    if (debug) println 'ajaxBaseUrlClips: ' + ajaxBaseUrlClips
                    def strAjaxClipsUrl = 'http://www.svtplay.se' + ajaxBaseUrlClips.replace("&amp;", "&") + 'sida=1&antal=' + antal
                    if (debug) println 'strAjaxClipsUrl: ' + strAjaxClipsUrl

                    def ajaxClipsUrl = new URL(''+strAjaxClipsUrl)
                                        
                    def ajaxClipsConnection = ajaxClipsUrl.openConnection()
                    if(ajaxClipsConnection.responseCode == 200){
                        strServiioClipsHTML = ajaxClipsConnection.content.text
                    }                                
                }
            }
            
            // Refresh the HTML used for article selection
            strNonLiveFeedHTML = strServiioEpisodesHTML + strServiioClipsHTML
            // Refresh the matches
            articleMatches = strNonLiveFeedHTML =~ '(?s)<article [^>]*?>.*?</article>'
        }
        
        if (debug) println ' articleMatches.getCount(): ' +  articleMatches.getCount()
        for( int i = 0; i < articleMatches.getCount() && (maxItemsToRetrieve == -1 || itemsAdded < maxItemsToRetrieve) ; i++ ) {
            if (debug) println 'articleMatches['+ i+ ']: ' + articleMatches[i]
            // Find out if this has a playlink
//            def playlinkMatcher = articleMatches[i][0] =~ '(?s).*data-title="([^"]*?)".*?<a href="([^"]*?)" class="playLink'
//            def playlinkMatcher = articleMatches[i] =~ '(?s)data-title=\"([^\"]*?)\".*?<a [^>]*?href=\"([^\"]*?)\"[^>]*?class=\"[^\"]*?(?:play_link|playLink|playAltLink|play_js-video-link)'
            def playlinkMatcher = articleMatches[i] =~ '(?s)data-title=\"([^\"]*?)\".*?<a [^>]*?href=\"([^\"]*?)\"[^>]*?class=\"[^\"]*?(?:play_link|playLink|playAltLink|play_js-video-link|play_js-videolist-element-link|play_js-videolist-element-link)'
            def playlinkTitleFieldPosition = 1
            def playlinkUrlFieldPosition = 2
            if (playlinkMatcher == null || playlinkMatcher.getCount() == 0)
            {
                if (debug) println 'Hittade inga standard playlinks. Försöker med alternativt mönster'
                playlinkMatcher = articleMatches[i] =~ '(?s)<a [^>]*?href=\"([^\"]*?)\"[^>]*?class=\"[^\"]*?(?:play_vertical-list__header-link)[^\"]*?\"[^>]*?>(.*?)</a>'
                playlinkTitleFieldPosition = 2
                playlinkUrlFieldPosition = 1
            }
            if (playlinkMatcher != null)
            {
                if (debug) println 'playlinkMatcher is not null for article with index ' + i + '. playlinkMatcher.getCount(): ' + playlinkMatcher.getCount() + '.'
                def webResourceItemTitle
                String strReleaseDate
                Date releaseDate
                def videoArticleThumbnailUrl
                def videoArticleUrl         
                
                def validDate = false       
                
                if (playlinkMatcher.getCount() > 0)
                {
                    // Try parsing the releas date
                    // <time datetime="2012-11-09T18:10+01:00">
                    log(' Found matching playlink')
                    if (debug) println ' Found matching playlink'
                    def timeMatcher = articleMatches[i] =~ '(?s)data-published=\"([^\"]*?)\"'
                    if (timeMatcher != null)
                    {
                        if (timeMatcher.getCount() > 0)
                        {
                            strReleaseDate = timeMatcher[0][1]
                            if (debug) println 'strReleaseDate: ' + strReleaseDate
                            if (strReleaseDate != null && strReleaseDate.length() > 0)
                            {
                                try
                                {
                                    releaseDate = svtPlayCommon.GetValidDate(strReleaseDate)
                                    validDate = true
                                    //log(' releaseDate: ' + releaseDate)
                                    if (debug)  println ' releaseDate: ' + releaseDate
                                }
                                catch(Exception ex)
                                {
                                     log('ERROR:  Could not parse release date (strReleaseDate: ' + strReleaseDate + ')')
                                      if (debug) println 'Error parsing date: ' + ex.getMessage()
                                }
                            }
                        }
                    }

                    // Try parsing the thumbnail
                    //<img class="playGridThumbnail" src="http://www.svt.se/cachable_image/1352482140000/svts/article574062.svt/ALTERNATES/medium/default_title"/>
                    def thumbnailMatcher = articleMatches[i] =~ '(?s)<img class=\"(?:play_videolist__thumbnail|playGridThumbnail|play_vertical-list__image)[^\"]*\" [^>]*?src=\"([^\"]*?)\"'
                    if (thumbnailMatcher != null)
                    {
                        if (thumbnailMatcher.getCount() > 0)
                        {
                            videoArticleThumbnailUrl = thumbnailMatcher[0][1]
                            
                            // Thumnail url:s may contain HTML escape sequences, which must be unescaped before being fed to Serviio.
                            if (videoArticleThumbnailUrl != null)
                            {
                                videoArticleThumbnailUrl = org.apache.commons.lang.StringEscapeUtils.unescapeHtml(videoArticleThumbnailUrl)
                                videoArticleThumbnailUrl = videoArticleThumbnailUrl.replaceAll(" ", "%20")
                                
                                def urlSpecialCharMatcher = videoArticleThumbnailUrl =~ "([åäöÅÄÖ])"
                                StringBuffer escapedThumbnailUrlBuffer = new StringBuffer()
                                while (urlSpecialCharMatcher.find()) 
                                {
                                   urlSpecialCharMatcher.appendReplacement(escapedThumbnailUrlBuffer, java.net.URLEncoder.encode(urlSpecialCharMatcher.group(0), "UTF-8"))
                                }
                                urlSpecialCharMatcher.appendTail(escapedThumbnailUrlBuffer)
                                videoArticleThumbnailUrl = escapedThumbnailUrlBuffer.toString()
//                                    videoArticleThumbnailUrl = videoArticleThumbnailUrl.replaceAll("([åäöÅÄÖ ])", java.net.URLEncoder.encode("\\$1"))
                                //videoArticleThumbnailUrl = (videoArticleThumbnailUrl =~ /([åäöÅÄÖ ])/).replaceAll(java.net.URLEncoder.encode("\$1"))
                                //videoArticleThumbnailUrl = java.net.URLEncoder.encode(videoArticleThumbnailUrl, "UTF-8")
                            }
                        
                            log(' videoArticleThumbnailUrl: ' + videoArticleThumbnailUrl)
                            if (debug) println ' videoArticleThumbnailUrl: ' + videoArticleThumbnailUrl
                        }
                    }
                    
                    webResourceItemTitle = org.apache.commons.lang.StringEscapeUtils.unescapeHtml(playlinkMatcher[0][playlinkTitleFieldPosition])
                    videoArticleUrl = 'http://www.svtplay.se' + playlinkMatcher[0][playlinkUrlFieldPosition]
                    log(' webResourceItemTitle: ' + webResourceItemTitle)
                    if (debug) println 'webResourceItemTitle: ' + webResourceItemTitle
                    log(' videoArticleUrl: ' + videoArticleUrl)
                    if (debug) println ' videoArticleUrl: ' + videoArticleUrl
 
                    WebResourceItem item = new WebResourceItem(title: webResourceItemTitle, additionalInfo: ['videoArticleUrl': videoArticleUrl, 'videoArticleThumbnailUrl': videoArticleThumbnailUrl, 'live': 'false'])
                    if (validDate)
                    {
                        item.releaseDate = releaseDate                        
                    }
                    items << item
                    itemsAdded++             
                }
            }
        }
        
        def webResourceContainer = new WebResourceContainer()
        webResourceContainer.title = title
        webResourceContainer.thumbnailUrl = thumbnailUrl
        webResourceContainer.items = items
        
        log( getExtractorName() + ': finished extracting items for url: ' +  resourceUrl + ' . Found ' + itemsAdded + ' items' )
        if (debug) println  getExtractorName() + ': finished extracting items for url: ' +  resourceUrl + ' . Found ' + itemsAdded + ' items'
        return webResourceContainer
        
    }

    ContentURLContainer extractUrl(WebResourceItem item, PreferredQuality requestedQuality) {
    
        log ( getExtractorName() + ': extracting Url for WebResourceItem at ' + item.getAdditionalInfo()['videoArticleUrl'])
        boolean blnIsLive = false
        def strIsLive = item.getAdditionalInfo()['live']
        if (strIsLive != null && strIsLive.equals("true"))
            blnIsLive = true

        return svtPlayCommon.extractUrl(new URL(item.getAdditionalInfo()['videoArticleUrl']), requestedQuality, item.getAdditionalInfo()['videoArticleThumbnailUrl'], blnIsLive, item.releaseDate)
    }
 }

/*
* Helper class, inherinting from AbstractUrlExtractor in order to get access to the shared log() method.
*
* This class will help extract stream URL:s from playable web page URL:S 
*/
class SvtPlayCommon extends AbstractUrlExtractor {

    boolean debug = true;
    
    private static SimpleDateFormat df = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mmz" )
    
    public SvtPlayCommon(boolean debug)
    {
        this.debug = debug
    }

    String getExtractorName() {
        return getClass().getName()
    }

    boolean extractorMatches(URL resourceUrl) {
        // Should never be called
        return (resourceUrl ==~'^http(s)?://www.svtplay.se/.*$') 
    }

    ContentURLContainer extractUrl(URL videoArticleUrl, PreferredQuality requestedQuality, String thumbnailUrl, boolean blnIsLive, Date releaseDate) {
            
        // Get the html page which displays the video widget
        def videoArticleContentHtml
        def connection = videoArticleUrl.openConnection()
        if(connection.responseCode == 200){
            videoArticleContentHtml = connection.content.text
            //if (debug) println 'videoArticleContentHtml: \n' + videoArticleContentHtml
        }
        else{
            if (debug) println 'connection.url: ' + connection.url + ', connection.responseCode: ' + connection.responseCode
            return null
        }

        // Parse the json resource url from the data-json attribute.
        def videoJsonMatcher = (videoArticleContentHtml =~ '(?s)<a [^>]*?id=\"player\\_[0-9]*\"[^>]*?data-json-href=\"([^\\"]*?)\"')
        // The above does not work as of 2016-03-11. The value of the data-json-href attribute does NOT contain a link to the JSON file.
        // So, we set it to NULL and  go into the else-clause below.
        videoJsonMatcher = null
        
        def videoJsonPath
        def videoJsonUrl
        if (videoJsonMatcher != null && videoJsonMatcher.size() > 0)
        {
            if (debug) println ' videoJsonMatcher[0][1]: ' +  videoJsonMatcher[0][1]
            videoJsonUrl = new URL('http://www.svtplay.se' + videoJsonMatcher[0][1] + '?output=json')
            if (debug) println 'videoJsonUrl: ' + videoJsonUrl
        }
        else
        {
            //<a id="player" class="svtplayer playJsRemotePlayer playSvtplayer" data-id="547626"
            def videoArticleMatcher = (videoArticleContentHtml =~ '(?s)<a [^>]*?id=\"player\\_[0-9]*\"[^>]*?data-id=\"([^\\"]*?)\"')
            def videoArticleId
            assert videoArticleMatcher != null
            if(videoArticleMatcher.size() > 0)
            {
                videoArticleId = videoArticleMatcher[0][1]
                log(' videoArticleId - ' + videoArticleId)
                if (debug) println ' videoArticleId - ' + videoArticleId
            }
            else
            {
            
                log(' No video article id or json href found')
                if (debug) println ' No video article id found'
                return null
            }
            videoJsonUrl = new URL('http://www.svtplay.se/video/' + videoArticleId + '?output=json')
        }
        log('videoJsonUrl: ' + videoJsonUrl)
        if (debug) println 'videoJsonUrl: ' + videoJsonUrl
        def videoJsonContent
        def connectionJson = videoJsonUrl.openConnection()
        if(connectionJson.responseCode == 200){
            videoJsonContent = connectionJson.content.text
        }
        else{
            return null
        }
        log(' videoJsonContent: ' + videoJsonContent)
        if (debug)   println ' videoJsonContent: ' + videoJsonContent

        // Find the url of the script from which we can parse the swfUrl
        // <script type="text/javascript" src="/public/2012.65/javascripts/script-built.js"></script>
        def swfUrl
        def scriptPathMatcher = (videoArticleContentHtml =~ '<script [^>]* src="([^\\"]*?\\/script-built\\.js)"')
        def scriptPath
        if(scriptPathMatcher != null && scriptPathMatcher.size() > 0)
        {
            scriptPath = scriptPathMatcher[0][1]
            log(' js scriptPath: ' + scriptPath)
            def scriptContent
            URL scriptUrl = new URL('http://www.svtplay.se' + scriptPath)
            def scriptConnection = scriptUrl.openConnection()
            if(scriptConnection.responseCode == 200){
                scriptContent = scriptConnection.content.text
                // svtplayer.SVTPlayer.SWF_PATH="/statiskt/swf/video/";svtplayer.SVTPlayer.SPRITE_PATH="/statiskt/skins/svt/css/img/svtplayer/";svtplayer.SVTPlayer.PLAYER_FILE="svtplayer-2012.48.swf";svtplayer.SVTPlayer.BUILD_VERSION="2012.48";    
                def swfUrlMatcher = scriptContent =~ 'svtplayer\\.SVTPlayer\\.PLAYER_SWF="([^\\"]*?)"'
                if(swfUrlMatcher != null && swfUrlMatcher.size() > 0)
                {
                    swfUrl = 'http://www.svtplay.se' + swfUrlMatcher[0][1] 
                    log(' swfUrl: ' + swfUrl)
                }
            }
        }
        else
        {
           log( ' Found no path to js script')
        }        

        def contentUrl = GetBestMatch(videoJsonContent, requestedQuality, videoArticleUrl)  
        if (contentUrl == null)
            return null
                              
//        if (swfUrl != null && contentUrl.startsWith('rtmp')) contentUrl = '"' + contentUrl + ' swfUrl=' + swfUrl + ' swfVfy=1 pageUrl=' + videoArticleUrl + '"'
        if (swfUrl != null && contentUrl.startsWith('rtmp')) contentUrl = contentUrl + ' swfUrl=' + swfUrl + ' swfVfy=1'
        
        
//        println 'blnIsLive: ' + blnIsLive
        
        if (thumbnailUrl == null || thumbnailUrl.length() == 0) {
            // <meta property="og:image" content="http://www.svt.se/barnkanalen/cachable_image/1401757500000/incoming/article45810.svt/ALTERNATES/extralarge/default_title"/>
            def matcherLogo = (videoArticleContentHtml =~ '(?s)<meta property="og:image" content="([^"]*?)"')
            if(matcherLogo.size() > 0)
            {
                thumbnailUrl = matcherLogo[0][1]
                if (!thumbnailUrl.contains("http://")) thumbnailUrl = "http://www.svtplay.se" + thumbnailUrl
                
                // Thumnail url:s may contain HTML escape sequences, which must be unescaped before being fed to Serviio.
                if (thumbnailUrl != null) {
                    thumbnailUrl = org.apache.commons.lang.StringEscapeUtils.unescapeHtml(thumbnailUrl)
                }
            }
        }
        if (thumbnailUrl != null) {
            thumbnailUrl = (thumbnailUrl =~ '(/ALTERNATES/extralarge/)').replaceAll('/ALTERNATES/medium/')
        }

        
        ContentURLContainer container =  new ContentURLContainer(fileType: MediaFileType.VIDEO, contentUrl: contentUrl, thumbnailUrl: thumbnailUrl, live: blnIsLive)
        if (blnIsLive) {
            // Set a cache key and mark this as expired on its startTime
            def strCacheKey = "" + videoArticleUrl + '_' + requestedQuality
//            println 'strCacheKey: ' + strCacheKey
            container.cacheKey = strCacheKey
            container.expiresOn = releaseDate
        }
        return container
    }
    
    String GetBestMatch(String jsonContent, PreferredQuality requestedQuality, URL videoUrl){
        String match = null
        String[] priorityList
        if(requestedQuality == PreferredQuality.HIGH){
            priorityList = ['e','d','c','b','a']
        }else{
            if(requestedQuality == PreferredQuality.MEDIUM){
                priorityList = ['c','b','d','a','e']
            }else{
                // LOW
                priorityList = ['a','b','c','d','e']
            }
        }       
        
        // example of match: rtmp://fl11.c91005.cdn.qbrick.com/91005/_definst_/kluster/20110922/PG-1146034-001A-MITTINATUREN2-02-mp4-e-v1.mp4 
        priorityList.each{
            if(match == null){
                def matcher = (jsonContent =~ '.*"url"\\:"(rtmp.*-'+it+'-v1.*?)","bitrate"\\:')
                if(matcher.size() > 0)
                {
                    match = matcher[0][1]
                    log(' found qbrick rtmp url. Best match for requested quality: ' + match)
                }
            }
        }
        
        if (match == null)
        {
            // This might be an AKAMAI video.
            // {"url":"http://svtplay1n-f.akamaihd.net/z/world/open/20121113/1308899-062A_ABC/REGIONALA_NYHET-062A-abc-4fc14b301c7a9fe6_,900,320,420,620,1660,2760,.mp4.csmil/manifest.f4m","bitrate":0,"playerType":"flash"}
            // {"url":"http://svtplay9i-f.akamaihd.net/i/world/open/20121230/1130774-003A/OLOF_PALME-003A-13e3178289b3be47_,900,320,420,620,1660,2760,.mp4.csmil/master.m3u8","bitrate":0,"playerType":"ios"}
            
                // We prefer the hls stream (ios) because ffmpeg has support for it and it is never encrypted
                def httpMatcher = (jsonContent =~ '\\{"url"\\:[^\\"]?"(http[^\\"]*?)","bitrate"[^}]*?"playerType"\\:"ios"\\}')
                // the flash version is more likely to be unplayable by ffmpeg, but we can try it.
                if(httpMatcher == null || httpMatcher.size() == 0)
                    httpMatcher = (jsonContent =~ '\\{"url"\\:[^\\"]?"(http[^\\"]*?)","bitrate"[^}]*?"playerType"\\:"flash"\\}')
                if(httpMatcher.size() > 0)
                {

                    String[] priorityListHttp
                    if(requestedQuality == PreferredQuality.HIGH){
                        priorityListHttp = ['1280x720','1024x576','768x432','704x396','576x324','512x288','480x270','320x180']
                    }else{
                        if(requestedQuality == PreferredQuality.MEDIUM){
                            priorityListHttp = ['768x432','704x396','576x324','1024x576','512x288','480x270','1280x720','320x180']
                        }else{
                            // LOW
                            priorityListHttp = ['320x180','480x270','512x288','576x324','704x396','768x432','1024x576', '1280x720']
                        }
                    }       

                    def httpMatch = httpMatcher[0][1]
                    if (debug)  println 'httpMatch: ' + httpMatch
                    
                    if (httpMatch.contains("m3u8"))
                    {
//                        println 'httpMatch är en m3u8-fil'
                        def m3u8URL = new URL(httpMatch)
                        def m3u8Connection = m3u8URL.openConnection()
                        if (m3u8Connection.responseCode == 200){
                            def  m3u8Content = m3u8Connection.content.text
                            //if (debug) println 'm3u8Content: ' + m3u8Content
                            List m3u8Lines = m3u8Content.readLines()
                            priorityListHttp.each{
                                if(match == null){
                                    if (debug) println 'priority resolution: ' + it
                                    for(int index = 0; index < m3u8Lines.size() - 1; index++)
                                    {
                                        //if (debug)  println '      ' + index + ': ' + m3u8Lines.get(index);
                                        def httpm3u8QualityMatcher = (m3u8Lines.get(index) =~ '#EXT-X-STREAM-INF\\:.*,RESOLUTION=' + it +',')
                                        if(httpm3u8QualityMatcher.size() > 0 )
                                        {
                                            match = m3u8Lines.get(index+1)
                                            
                                            // clean up ugly stuff in the url
                                            match = (match =~ /\\?null=&/).replaceAll('')
                                            match = (match =~ /&id=$/).replaceAll('')
                                            log(' found m3u8 http stream url. Best match for requested quality: ' + match)
                                            if (debug)  println ' found m3u8 http stream url. Best match for requested quality: ' + match
                                        }
                                    }
                                }                                    
                            }
                        }
                        
                        // Make sure the contentURl is absolute
                        if (match != null)
                        {
                            if (!match.startsWith('http') && !match.startsWith('rtmp'))
                            {
                                if (debug) println('match is relative. Attempt to parse base url')
                                def m3u8Position = httpMatch.indexOf("m3u8")
                                if (debug) println('m3u8Position: ' + m3u8Position)
                                def baseM3u8UrlEndPosition = httpMatch.lastIndexOf("/", m3u8Position)
                                if (debug) println('baseM3u8UrlEndPosition: ' + baseM3u8UrlEndPosition)
                                def baseM3u8Url = httpMatch.substring(0, baseM3u8UrlEndPosition + 1)
                                if (debug) println('baseM3u8Url: ' + baseM3u8Url)
                                match = baseM3u8Url + match
                                if (debug) println('match after being appended to base url: ' + match)
                            }
                        }                     
                    }
                    
                    if (match == null)
                    {
                        // Använd Pirate API
                    
                        // En bugg i pirateplay API:t gör att vi behöver filtrera bort ovidkommande klipp.
                        // Här hittar vi en mall för vad vi ska leta efter bland de strömmar som pirateplay föreslår
                        def httpMatchIntro = (httpMatch =~ /manifest\.f4m$/).replaceAll('')
                        httpMatchIntro = (httpMatchIntro =~ /master\.m3u8$/).replaceAll('')
                        //println 'httpMatchIntro: ' + httpMatchIntro
                    
                    
                        // Get the url from the pirateplayer API
                        def pirateURL = new URL('http://pirateplay.se/api/get_streams.js?url=' + java.net.URLEncoder.encode(videoUrl.toString()))
                        // println 'pirateURL: ' + pirateURL
                     
                        def pirateContent
                           
                        def pirateConnection = pirateURL.openConnection()
                        if (pirateConnection.responseCode == 200){
                            pirateContent = pirateConnection.content.text    
                            //println 'pirateContent: ' + pirateContent
                                            
                            priorityListHttp.each{
                                if(match == null){
                                    def httpPirateMatcher = (pirateContent =~ '"url"\\:[^\\"]?"(http[^\\"]*?)"[^\\}]*?"quality": "' + it +'"')
                                    if(httpPirateMatcher.size() > 0)
                                    {
                                        for(int i=0; i < httpPirateMatcher.size(); i++)
                                        {
                                            //println 'httpPirateMatcher[i][1]: ' + httpPirateMatcher[i][1]
                                            if (httpMatchIntro.length() > 0 && httpPirateMatcher[i][1].startsWith(httpMatchIntro))
                                            {
                                                match = httpPirateMatcher[i][1]
                                                
                                                // clean up ugly stuff in the url
                                                match = (match =~ /\\?null=&/).replaceAll('')
                                                match = (match =~ /&id=$/).replaceAll('')
                                                log(' found pirateAPI http stream url based matching the svtplay json url entry. Best match for requested quality: ' + match)
                                                //println ' found pirateAPI http url based on svtplay json. Best match for requested quality: ' + match
                                             }
                                        }
                                    }
                                }
                            }
                            
                            if(match == null){
                                priorityListHttp.each{
                                    if(match == null){
                                        def httpPirateMatcher = (pirateContent =~ '"url"\\:[^\\"]?"(http[^\\"]*?)"[^\\}]*?"quality": "' + it +'"')
                                        if(httpPirateMatcher.size() > 0)
                                        {
                                            match = httpPirateMatcher[0][1]
                                            
                                            // clean up ugly stuff in the url
                                            match = (match =~ /\\?null=&/).replaceAll('')
                                            match = (match =~ /&id=$/).replaceAll('')
                                            log(' found http pirateAPI stream url NOT based on svtplay json entry. Best match for requested quality: ' + match)
                                            //println ' found http pirate stream url NOT based on svtplay json entry. Best match for requested quality: ' + match
                                        }
                                    }
                                }
                            }
    
                            if (match == null) {
                                // The stream might be dynamic. Just grab the first one.
                                def defaultMatcher = (pirateContent =~ '"url"\\:[^\\"]?"(http[^\\"]*?)"')
                                if(defaultMatcher.size() > 0)
                                {
                                    match = defaultMatcher[0][1]
                                    
                                    // clean up ugly stuff in the url
    //                                match = (match =~ /\\?null=&/).replaceAll('')
    //                                match = (match =~ /&id=$/).replaceAll('')
                                    log(' found default http stream url, not matched for quality: ' + match)
                                    // Nota bene: this is most likely a f4m manifest, which is not playable in ffmpeg.
                                    // Need to work on hds decoding?
                                    // Ticket #1964 has been opened with ffmpeg!: 
                                    // https://ffmpeg.org/trac/ffmpeg/ticket/1964
                                }
    
                            }
                        }
                    }
                }
        }
                        
        return match
    }

    Date GetValidDate(strMarkupDate)
    {
        // Reformat date from html markup to parsable date string
        //NOTE: SimpleDateFormat uses GMT[-+]hh:mm for the TZ which breaks
        //things a bit.  Before we go on we have to repair this.

        //this is zero time so we need to add that TZ indicator for 
        if ( strMarkupDate.endsWith( "Z" ) ) {
            strMarkupDate = strMarkupDate.substring( 0, strMarkupDate.length() - 1) + "GMT-00:00";
        } else {
            int inset = 6;
        
            String s0 = strMarkupDate.substring( 0, strMarkupDate.length() - inset );
            String s1 = strMarkupDate.substring( strMarkupDate.length() - inset, strMarkupDate.length() );

            strMarkupDate = s0 + "GMT" + s1;
        }
        
        // println 're-structured strRelease: ' + strReleaseDate
        
        return df.parse( strMarkupDate );
    }    
}

class SvtPlayRss extends FeedItemUrlExtractor {

    final boolean debug = false;

    SvtPlayCommon svtPlayCommon = new SvtPlayCommon(debug)

    final VALID_RESOURCE_URL = '^http(s)?://www.svtplay.se/.*.xml$'

    final int VERSION = 1;

    String getExtractorName() {
        return getClass().getName()
    }

    int getVersion() {
        return VERSION
    }
    
    boolean extractorMatches(URL resourceUrl) {
        return (resourceUrl ==~ VALID_RESOURCE_URL)
    }
    
    ContentURLContainer extractUrl(java.util.Map<String,URL> links, PreferredQuality requestedQuality) {
       // link
       URL videoArticleUrl = links.get("default")
       URL thumnailImageUrl = links.get("thumbnailImage")

       String strThumnailImageUrl = null
       if (thumnailImageUrl != null)
       {
           strThumnailImageUrl = thumnailImageUrl.toString()
       }
       
       log('SvtPlayRss.extractUrl, videoArticleUrl: ' + videoArticleUrl)
       log('SvtPlayRss.extractUrl, strThumnailImageUrl: ' + strThumnailImageUrl)
       return svtPlayCommon.extractUrl(videoArticleUrl, requestedQuality, strThumnailImageUrl, false, null)
        
    }
}