FAQ  •  Register  •  Login

YouTube plugin using Data API V3

<<

stefana888

User avatar

DLNA master

Posts: 134

Joined: Mon Aug 18, 2014 6:24 am

Post Sat Apr 25, 2015 5:02 pm

YouTube plugin using Data API V3

Plugin type Webresource

One simple url:


https://www.googleapis.com/youtube/v3/playlistItems?playlistId=YOUR_PLAYLIST_ID_HERE

Note: The url covers channel uploads since everything is playlists.

A Google YouTube API key is needed in code below.

  Code:
import org.serviio.library.metadata.*
import org.serviio.library.online.*
import groovy.json.*

/**
 * YouTube.com content URL extractor plugin.
 *
 * API call playlistItems.list is implemented
 * Using youtube_dl with best (default) quality
 * 
 * @author Stefan Andersson
 *
 **/

class YouTube extends WebResourceUrlExtractor {

    final VALID_RESOURCE_URL = '^https?://www.googleapis.com/youtube/.*$'
       
    String getExtractorName() {
        return getClass().getName()
    }
   
    boolean extractorMatches(URL feedUrl)
    {
        return feedUrl ==~ VALID_RESOURCE_URL
    }
   
    WebResourceContainer extractItems(URL resourceUrl, int maxItemsToRetrieve)     
    {
      final user_agent = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"
      final api_key = "YOUR_API_KEY_HERE"

      if (maxItemsToRetrieve == -1) maxItemsToRetrieve = 50

      def apiUrl = new URL(resourceUrl.toString() + "&part=snippet" + "&maxResults=" + maxItemsToRetrieve.toString() + "&key=" + api_key)

      def json = new JsonSlurper().parseText(openURL(apiUrl, user_agent))

      def items = []

      json.items.each()
      {
         if (it.snippet.title != "Deleted video")
         {
            items.add(new WebResourceItem(title : it.snippet.title,
                                                               additionalInfo : ['videoId' : it.snippet.resourceId.videoId,
                                                               'thumb' : it.snippet.thumbnails.high.url]))

            log("title: ${it.snippet.title}")
            log("videoId: ${it.snippet.resourceId.videoId}")
            log("thumb: ${it.snippet.thumbnails.standard.url}")
         }
      }

      return new WebResourceContainer(title : "", items: items)
    }
     
    ContentURLContainer extractUrl(WebResourceItem item, PreferredQuality requestedQuality)
    {
      def linkUrl = "http://www.youtube.com/watch?v=${item.additionalInfo['videoId']}"
      def thumbnailUrl = item.additionalInfo['thumb']
   
      .
      .
      .
      .


 }
<<

zip

User avatar

Serviio developer / Site Admin

Posts: 16232

Joined: Sat Oct 24, 2009 12:24 pm

Location: London, UK

Post Sun Apr 26, 2015 1:48 pm

Re: YouTube plugin using Data API V3

Thanks, I've updated the main plugin based on your code. hope it's ok.

viewtopic.php?f=20&t=3276
<<

stefana888

User avatar

DLNA master

Posts: 134

Joined: Mon Aug 18, 2014 6:24 am

Post Wed Apr 29, 2015 7:00 pm

Re: YouTube plugin using Data API V3

Yes. I see now that GET 'videos' can be used with the same code. I was mixing that up with GET 'search'.


Edit: No, it cannot be usd with same code now.
Last edited by stefana888 on Tue May 12, 2015 4:19 pm, edited 1 time in total.
<<

kerulen

User avatar

Serviio newbie

Posts: 1

Joined: Sat May 09, 2015 11:38 am

Post Sat May 09, 2015 12:38 pm

Re: YouTube plugin using Data API V3

Hi,

I have made few changes to support the latest uploads by referencing the channel name:
Now, with verision 2, you can supply a PLAYLIST_ID.
With this version 3 you can specify the Channel Name and it retrieves the Uploads Playlist for that channel.

  Code:
https://www.googleapis.com/youtube/v3/channels?forUsername=CHANNEL_NAME


To obtain the "real and exact" Channel Name, you can go to the channel an click on "videos" menu.
Then you can look for an URL like:

  Code:
https://www.youtube.com/user/CHANNEL_NAME/videos

Example:

  Code:
https://www.youtube.com/user/EpicTVadventure/videos

In Serviio you must create a Web Resource like this:

  Code:
https://www.googleapis.com/youtube/v3/channels?forUsername=CHANNEL_NAME

Examples:

  Code:
EpicTVadventure
https://www.googleapis.com/youtube/v3/channels?forUsername=EpicTVadventure
GoProCamera
https://www.googleapis.com/youtube/v3/channels?forUsername=GoProCamera


This is the piece of code I have changed...

  Code:
      ...

   @Override
   WebResourceContainer extractItems(URL resourceUrl, int maxItemsToRetrieve) {
      final user_agent = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"
      final api_key = "AIzaSyAPeRAlHMSa_WQMbarc_ffse7-t0-DOuVQ" // this is Serviio API key, don't use it for anything else please, they are easy to get

      if (maxItemsToRetrieve == -1) maxItemsToRetrieve = 50
    
     if (resourceUrl.toString().contains("channels")){
      def channelUrl = new URL(resourceUrl.toString() + "&part=contentDetails" + "&maxResults=" + maxItemsToRetrieve + "&key=" + api_key) 
      def channeljson = new JsonSlurper().parseText(openURL(channelUrl, user_agent))
      def uploadPlaylist = channeljson.items[0].contentDetails.relatedPlaylists.uploads
         
      resourceUrl = new URL("https://www.googleapis.com/youtube/v3/playlistItems?playlistId=$uploadPlaylist")
     }
          
      def apiUrl = new URL(resourceUrl.toString() + "&part=snippet" + "&maxResults=" + maxItemsToRetrieve + "&key=" + api_key)

       ...



I have attached "version 3" Youtube plugin to this post.

This works for me! ;) :D

Serviio: 1.5.2
O.S: Windows 8.1
Youtube Plugin: version 3


Youtube.groovy
Serviio Youtube Plugin version 3
(8.42 KiB) Downloaded 364 times
<<

stefana888

User avatar

DLNA master

Posts: 134

Joined: Mon Aug 18, 2014 6:24 am

Post Sat May 09, 2015 1:08 pm

Re: YouTube plugin using Data API V3

Well, it is always possible to make a suitable abstraction of the Youtube API v3, e.g. take channel names in playlist input. To support a specified number of result pages (more than 50 items) in the input would also be interesting.
<<

tuareg64ve

Serviio lover

Posts: 54

Joined: Wed Mar 28, 2012 4:38 am

Post Sat May 09, 2015 9:49 pm

Re: YouTube plugin using Data API V3

For linux users, remind to set the correct youtube-dl path in the following line:

  Code:
final YOUTUBE_DL = "/usr/local/bin/youtube-dl"


Could you please tell me how to test a playlist?
The following lines:

  Code:
    static void main(args) {
        // this is just to test
        YouTubeDL extractor = new YouTubeDL()

        assert extractor.extractorMatches( new URL("https://www.youtube.com/watch?v=AJtDXIazrMo&list=PLDcnymzs18LVXfO_x0Ei0R24qDbVtyy66") )

        WebResourceContainer container = extractor.extractItems( new URL("https://www.youtube.com/watch?v=AJtDXIazrMo&list=PLDcnymzs18LVXfO_x0Ei0R24qDbVtyy66"), 10)
        println container
        ContentURLContainer result = extractor.extractUrl(container.getItems()[2], PreferredQuality.MEDIUM)
        println result
    }


test a single video, right?
How can modify them for a playlist?
Sorry, I am not so much familiar with Groovy, I am trying to learn how to use it.
Thanks
<<

stefana888

User avatar

DLNA master

Posts: 134

Joined: Mon Aug 18, 2014 6:24 am

Post Sun May 10, 2015 2:26 am

Re: YouTube plugin using Data API V3

Maybe this is the wrong thread... youtube-dl uses identical URLs with that in the web browser for selecting a playlist. Not to mix up with the Youtube API.
<<

stefana888

User avatar

DLNA master

Posts: 134

Joined: Mon Aug 18, 2014 6:24 am

Post Tue May 12, 2015 6:00 pm

Re: YouTube plugin using Data API V3

New working version of the YouTube plugin

Changes:

1. Videos bug fixed
Now handles different Json result for videos and playlist id's without causing error

2. Channel names (user name) added:
  Code:
https://www.googleapis.com/youtube/v3/channels?forUsername=USER_NAME

** Test code added in main **

  Code:
import groovy.json.JsonSlurper

import org.serviio.library.metadata.*
import org.serviio.library.online.*

/**
 * YouTube.com content URL extractor plugin.
 *
 * It uses YouTube API v3 for retrieving playlists.
 *
 * @see http://en.wikipedia.org/wiki/Youtube#Quality_and_codecs
 * 
 * @author Petr Nejedly
 *
 */
class YouTube extends WebResourceUrlExtractor {

    final VALID_RESOURCE_URL = '^https?://www.googleapis.com/youtube/.*$'
   /* Listed in order of quality */
    final availableFormats = ['37', '46', '22', '45', '35', '34', '18', '44', '43', '6', '5']
   
    String getExtractorName() {
        return getClass().getName()
    }
   
    boolean extractorMatches(URL feedUrl) {
        return feedUrl ==~ VALID_RESOURCE_URL
    }
   
   public int getVersion() {
      return 3;
   }
   
   @Override
   WebResourceContainer extractItems(URL resourceUrl, int maxItemsToRetrieve) {
      final user_agent = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"
      final api_key = "AIzaSyAPeRAlHMSa_WQMbarc_ffse7-t0-DOuVQ" // this is Serviio API key, don't use it for anything else please, they are easy to get

      if (maxItemsToRetrieve == -1) maxItemsToRetrieve = 50
   
      // Handle channel name urls
      if (resourceUrl.toString().contains("channels")) {
          def channelUrl = new URL(resourceUrl.toString() + "&part=contentDetails" + "&key=" + api_key) 
     def channeljson = new JsonSlurper().parseText(openURL(channelUrl, user_agent))
     def uploadPlaylist = channeljson.items[0].contentDetails.relatedPlaylists.uploads
         
     resourceUrl = new URL("https://www.googleapis.com/youtube/v3/playlistItems?playlistId=$uploadPlaylist")
      }    
          
      def apiUrl = new URL(resourceUrl.toString() + "&part=snippet" + "&maxResults=" + maxItemsToRetrieve + "&key=" + api_key)

      def json = new JsonSlurper().parseText(openURL(apiUrl, user_agent))

      def items = []

      json.items.each() {
         if (it.snippet.title != "Deleted video") {
             items.add(new WebResourceItem(title : it.snippet.title,
                                           additionalInfo : ['videoId' : resourceUrl.toString().contains("videos") ? it.id : it.snippet.resourceId.videoId,
                                                             'thumb' : it.snippet.thumbnails.high.url]))
         }
      }
    
     def containerThumbnailUrl = items?.find { it -> it.additionalInfo['thumb'] != null }?.additionalInfo['thumb']

      return new WebResourceContainer(items: items, thumbnailUrl: containerThumbnailUrl)
    }

   @Override
   protected ContentURLContainer extractUrl(WebResourceItem wrItem, PreferredQuality requestedQuality) {
      
        def contentUrl
      def expiryDate
      def expiresImmediately
      def cacheKey
      
        def videoId = wrItem.additionalInfo['videoId']
      def thumbnailUrl = wrItem.additionalInfo['thumb']
      
        for (elType in ['&el=embedded', '&el=detailpage', '&el=vevo', '']) {           
            def videoInfoUrl = "http://www.youtube.com/get_video_info?&video_id=$videoId$elType&ps=default&eurl=&gl=US&hl=en"
            //log("Loading video info: $videoInfoUrl")
            def videoInfoWebPage = new URL(videoInfoUrl).getText()
            def parameters = [:]
       
            videoInfoWebPage.split('&').each{item -> addParameter(item, parameters, '=')}
            if(parameters.containsKey('token')) {      
               def formatUrlMapString = parameters['fmt_url_map']   
            def urlEncodedUrlMapString = parameters['url_encoded_fmt_stream_map']
            def adaptiveFormatsMapString = parameters['adaptive_fmts']
            def allFormatUrlMap = [:]
            
            def test = URLDecoder.decode(adaptiveFormatsMapString,'UTF-8').split(',').each{item -> addParameter(item, allFormatUrlMap, '\\|')}
            
            if (formatUrlMapString != null && formatUrlMapString.length() > 0 ) {
               URLDecoder.decode(formatUrlMapString,'UTF-8').split(',').each{item -> addParameter(item, allFormatUrlMap, '\\|')}                  
            } else {            
               if (urlEncodedUrlMapString != null && urlEncodedUrlMapString.length() > 0 ) {                
                  processStreamData(urlEncodedUrlMapString, allFormatUrlMap)
               }   
               if (adaptiveFormatsMapString != null && adaptiveFormatsMapString.length() > 0 ) {
                  processStreamData(adaptiveFormatsMapString, allFormatUrlMap)
               }
            }
            // get available formats for requested quality, sorted by quality from highest
            def formatUrlMap = new LinkedHashMap()
            if(requestedQuality == PreferredQuality.HIGH) {
               // best quality, get the first from the list
               sortAvailableFormatUrls(availableFormats, allFormatUrlMap, formatUrlMap)
               def selectedUrl = formatUrlMap.entrySet().toList().head()
               contentUrl = selectedUrl.getValue()
               cacheKey = getCacheKey(videoId, selectedUrl.getKey())
            } else if (requestedQuality == PreferredQuality.MEDIUM) {
               // work with subset of available formats, starting at the position of format 35 and then take the best quality from there
               sortAvailableFormatUrls(availableFormats.getAt(4..availableFormats.size-1), allFormatUrlMap, formatUrlMap)
               def selectedUrl = formatUrlMap.entrySet().toList().head()
               contentUrl = selectedUrl.getValue()
               cacheKey = getCacheKey(videoId, selectedUrl.getKey())
            } else {
               // worst quality, take the last url
               sortAvailableFormatUrls(availableFormats, allFormatUrlMap, formatUrlMap)
               def selectedUrl = formatUrlMap.entrySet().toList().last()
               contentUrl = selectedUrl.getValue()
               cacheKey = getCacheKey(linkUrl, selectedUrl.getKey())
            }          
            if(contentUrl != null) {
               expiresImmediately = true
               if(contentUrl.startsWith('http')) {
                  // http URL
                  def contentUrlParameters = [:]
                  contentUrl.split('&').each{item -> addParameter(item, contentUrlParameters, '=')}
                  if( contentUrlParameters['expire'] != null ) {   
                     //log(Long.parseLong(contentUrlParameters['expire']).toString())
                     expiryDate = new Date(Long.parseLong(contentUrlParameters['expire'])*1000)
                     expiresImmediately = false
                  }
               } else {
                     // rtmp URL
                     def rtmpMatcher = contentUrl =~ 'rtmpe?://.*?/(.*)'
                  def app = rtmpMatcher[0][1]
                  // TODO load swf player URL from the HTML page
                     contentUrl = "$contentUrl app=$app swfUrl=http://s.ytimg.com/yt/swfbin/watch_as3-vflg0Q-LP.swf swfVfy=1"
               }   
            }
               
                break
            }
        }   
        return new ContentURLContainer(fileType: MediaFileType.VIDEO, contentUrl: contentUrl, thumbnailUrl: thumbnailUrl, expiresOn: expiryDate, expiresImmediately: expiresImmediately, cacheKey: cacheKey)
    }
 
    def addParameter(parameterString, parameters, separator) {
        def values = parameterString.split(separator)
        if( values.length == 2 ) {   
             parameters.put(values[0], values[1])
        }
    }   
   
   def processStreamData(String urlEncodedUrlMapString, Map streamsMap ) {
      URLDecoder.decode(urlEncodedUrlMapString,'UTF-8').split(',').each{item ->
         def streamParams = [:]
         item.split('&').each{item2 -> addParameter(item2, streamParams, '=')}
         String urlKeyName = streamParams.containsKey('url') ? 'url' : 'conn'
         //String stream = URLDecoder.decode(streamParams['stream'],'UTF-8') //TODO stream is playpath
         String streamUrl = URLDecoder.decode(streamParams[urlKeyName],'UTF-8')
         String signature = streamParams['sig']
         if(signature) streamUrl = streamUrl + "&signature=" + signature
         streamsMap.put(streamParams['itag'], streamUrl )
      }
   }

   def String getCacheKey(String videoId, String qualityId) {
      "youtube_${videoId}_${qualityId}"
   }
      
   def sortAvailableFormatUrls(List formatIds, Map sourceMap, Map targetMap) {
      formatIds.each{formatId ->
         if(sourceMap.containsKey(formatId) ) {            
             targetMap.put(formatId, sourceMap.get(formatId))
         }
      }
   }
   
    static void main(args) {
      // this is just to test
        YouTube extractor = new YouTube()
      
      assert extractor.extractorMatches( new URL("https://www.googleapis.com/youtube/v3/playlistItems?playlistId=YOUR_PLAYLIST_ID_HERE") )
      assert !extractor.extractorMatches( new URL("http://google.com/feeds/api/standardfeeds/top_rated?time=today") )
      
      
      WebResourceContainer container = extractor.extractItems( new URL("https://www.googleapis.com/youtube/v3/playlistItems?playlistId=PL5D6FB3CCE0053D01"), 10)
      println container
      ContentURLContainer result = extractor.extractUrl(container.getItems()[2], PreferredQuality.MEDIUM)
      println result
      
                println ""
                container = extractor.extractItems( new URL("https://www.googleapis.com/youtube/v3/videos?chart=mostPopular"), 10)
      println container
      result = extractor.extractUrl(container.getItems()[2], PreferredQuality.MEDIUM)
      println result

                println ""
                container = extractor.extractItems( new URL("https://www.googleapis.com/youtube/v3/channels?forUsername=NFL"), 10)
                println container
                result = extractor.extractUrl(container.getItems()[2], PreferredQuality.MEDIUM)
      println result
    }

}
Last edited by stefana888 on Fri May 15, 2015 4:37 pm, edited 2 times in total.
<<

tuareg64ve

Serviio lover

Posts: 54

Joined: Wed Mar 28, 2012 4:38 am

Post Tue May 12, 2015 9:20 pm

Re: YouTube plugin using Data API V3

This link is SkyNews live channel:

https://www.googleapis.com/youtube/v3/v ... w4hmqVPe0E

This request to API v3 works fine:

https://www.googleapis.com/youtube/v3/v ... entDetails

Putting in SERVIIO I get this issue:

  Code:
2015-05-12 23:15:31,935 WARN  [FeedUpdaterWorker] An error occured while parsing the online resource https://www.googleapis.com/youtube/v3/videos?id=sw4hmqVPe0E, will try again soon: Unexpected error while invoking plugin (YouTube): null
org.serviio.library.online.metadata.OnlineResourceParseException: Unexpected error while invoking plugin (YouTube): null
   at org.serviio.library.online.WebResourceParser.parse(WebResourceParser.java:78)
   at org.serviio.library.online.OnlineLibraryManager.findResource(OnlineLibraryManager.java:186)
   at org.serviio.library.online.OnlineLibraryManager.findResourceInCacheOrParse(OnlineLibraryManager.java:203)
   at org.serviio.library.online.metadata.FeedUpdaterWorker.getOnlineItems(FeedUpdaterWorker.java:191)
   at org.serviio.library.online.metadata.FeedUpdaterWorker.run(FeedUpdaterWorker.java:108)
   at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException
2015-05-12 23:15:32,668 DEBUG [SearchManager] Committing search index


any idea?
<<

stefana888

User avatar

DLNA master

Posts: 134

Joined: Mon Aug 18, 2014 6:24 am

Post Wed May 13, 2015 12:10 am

Re: YouTube plugin using Data API V3

tuareg64ve wrote:This link is SkyNews live channel:

https://www.googleapis.com/youtube/v3/v ... w4hmqVPe0E

any idea?


Don't know what causes that, the link works for me. Server restart??
<<

tuareg64ve

Serviio lover

Posts: 54

Joined: Wed Mar 28, 2012 4:38 am

Post Wed May 13, 2015 10:41 pm

Re: YouTube plugin using Data API V3

Unfortunately server restart does not help!
Have you tried that video in your Serviio ?
<<

stefana888

User avatar

DLNA master

Posts: 134

Joined: Mon Aug 18, 2014 6:24 am

Post Thu May 14, 2015 12:02 am

Re: YouTube plugin using Data API V3

tuareg64ve wrote:Unfortunately server restart does not help!
Have you tried that video in your Serviio ?


Yes I have tested it because I saw it was a live video. The problem occurs before the plugin code is entered. Something must be wrong with the Serviio setup or plugin installation. No Youtube at all is working, correct?

Note. The Youtube plugin assumes all videos are not live. It should play but same ffmpeg instance running after stop can eventually fill up disc. You may start another transcoding job or manually kill the ffmpeg process.
<<

tuareg64ve

Serviio lover

Posts: 54

Joined: Wed Mar 28, 2012 4:38 am

Post Thu May 14, 2015 6:37 am

Re: YouTube plugin using Data API V3

No Youtube at all is working, correct?


Correct.Any chance to fix it?
Is this a major issue or it could be fixed with few changes?
<<

stefana888

User avatar

DLNA master

Posts: 134

Joined: Mon Aug 18, 2014 6:24 am

Post Thu May 14, 2015 7:35 am

Re: YouTube plugin using Data API V3

tuareg64ve wrote:
No Youtube at all is working, correct?


Correct.Any chance to fix it?
Is this a major issue or it could be fixed with few changes?


Make sure plugintype is 'Web tesource'. If it still not work. Delete all plugins, restart server and reinstall plugins. You can also try reinstall Serviio.
<<

tuareg64ve

Serviio lover

Posts: 54

Joined: Wed Mar 28, 2012 4:38 am

Post Thu May 14, 2015 7:49 am

Re: YouTube plugin using Data API V3

stefana888 wrote:
tuareg64ve wrote:Make sure plugintype is 'Web tesource'. If it still not work. Delete all plugins, restart server and reinstall plugins. You can also try reinstall Serviio.


Plugintype is "Web resource".
I have just reinstalled Serviio and the plugins for the upgrade to 1.5.2.
Installation is on Linux, so there is no need of installation procedure. It is just a copy of files.
Everything is working except Youtube live streams.
Why should I have to do it again?
<<

stefana888

User avatar

DLNA master

Posts: 134

Joined: Mon Aug 18, 2014 6:24 am

Post Thu May 14, 2015 8:35 am

Re: YouTube plugin using Data API V3

tuareg64ve wrote:
stefana888 wrote:
tuareg64ve wrote:Make sure plugintype is 'Web tesource'. If it still not work. Delete all plugins, restart server and reinstall plugins. You can also try reinstall Serviio.


Plugintype is "Web resource".
I have just reinstalled Serviio and the plugins for the upgrade to 1.5.2.
Installation is on Linux, so there is no need of installation procedure. It is just a copy of files.
Everything is working except Youtube live streams.
Why should I have to do it again?

Sky news live works for me.
<<

tuareg64ve

Serviio lover

Posts: 54

Joined: Wed Mar 28, 2012 4:38 am

Post Thu May 14, 2015 9:08 am

Re: YouTube plugin using Data API V3

stefana888 wrote:Sky news live works for me.


NFL works for me, but Sky News Live still does not work.
Could you post me your version of Youtube.groovy ?
<<

stefana888

User avatar

DLNA master

Posts: 134

Joined: Mon Aug 18, 2014 6:24 am

Post Thu May 14, 2015 10:27 am

Re: YouTube plugin using Data API V3

tuareg64ve wrote:
stefana888 wrote:Sky news live works for me.


NFL works for me, but Sky News Live still does not work.
Could you post me your version of Youtube.groovy ?


I now see that there is a problem with the official plugin (in the extractor) on that live video. This is not related to the Youtube v3 API. I use a selfmade v3 API youtube-dl tool-based plugin that suits my specific needs. You could get a youtube-dl tool-based plugin too from the available plugins section (YoutubeDL).
<<

tuareg64ve

Serviio lover

Posts: 54

Joined: Wed Mar 28, 2012 4:38 am

Post Thu May 14, 2015 11:31 am

Re: YouTube plugin using Data API V3

I have already installed YoutubeDL.groovy
Now I do not have time to check, but I will check and let you know
Thanks
<<

stefana888

User avatar

DLNA master

Posts: 134

Joined: Mon Aug 18, 2014 6:24 am

Post Thu May 14, 2015 11:48 am

Re: YouTube plugin using Data API V3

tuareg64ve wrote:I have already installed YoutubeDL.groovy
Now I do not have time to check, but I will check and let you know
Thanks


......http live streaming (HLS) was not supported in the extractor of the official plugin. Live content needs to be added.....
Next

Return to Plugin development

Who is online

Users browsing this forum: No registered users and 1 guest

Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group.
Designed by ST Software for PTF.