Support live streams
Now the type of a video is defined when trying to play a video so that live streams (.m3u8) are directly played by Kodi (no download required). We are able to know if a video is live from the response of the "list" REST API but it was decided to ignore this information in order to have a single action to play video (whether it is live or not). The goal was to keep the API of the add-on as simple as possible so that externel users only have to call the add-on's API with the ID of a video, without having to add the type of the video. The information about the type of the video will anyway be available in the response used to get the URL of a video so this solution does not impact the performance.
This commit is contained in:
parent
ed39c453e9
commit
4f8af35035
28
README.md
28
README.md
|
@ -2,21 +2,22 @@ A Kodi add-on for watching content hosted on [PeerTube](http://joinpeertube.org/
|
||||||
|
|
||||||
This add-on is under development so only basic features work, and you're
|
This add-on is under development so only basic features work, and you're
|
||||||
welcome to improve it.
|
welcome to improve it.
|
||||||
See [contribution guidelines](contributing.md) and
|
If you want to contribute, please start with the
|
||||||
[pending issues](https://framagit.org/StCyr/plugin.video.peertube/-/issues) to
|
[contribution guidelines](contributing.md) and the
|
||||||
start.
|
[pending issues](https://framagit.org/StCyr/plugin.video.peertube/-/issues).
|
||||||
|
|
||||||
[[_TOC_]]
|
[[_TOC_]]
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
* Browse all videos on a PeerTube instance
|
* Play videos (including live videos)
|
||||||
|
* Browse the videos on a PeerTube instance
|
||||||
* Search for videos on a PeerTube instance
|
* Search for videos on a PeerTube instance
|
||||||
* Select the PeerTube instance to use
|
* Select the PeerTube instance to use
|
||||||
* Select the preferred video resolution: the plugin will try to play the select
|
* Select the preferred video resolution: the plugin will try to play the
|
||||||
video resolution.
|
preferred video resolution.
|
||||||
If it's not available, it will play the lower resolution that is the closest
|
If it's not available, it will play the lower resolution that is the closest
|
||||||
from your preference.
|
to your preference.
|
||||||
If not available, it will play the higher resolution that is the closest from
|
If not available, it will play the higher resolution that is the closest from
|
||||||
your preference.
|
your preference.
|
||||||
|
|
||||||
|
@ -24,9 +25,10 @@ start.
|
||||||
|
|
||||||
* Preferred PeerTube instance
|
* Preferred PeerTube instance
|
||||||
* Preferred video resolution
|
* Preferred video resolution
|
||||||
* Number of videos to display per page
|
* Number of items to display per page
|
||||||
* Sort method to be used when listing videos (Currently, only 'views' and
|
* Value used to sort items when listing/searching videos:
|
||||||
'likes')
|
* `views`: sort by number of views (ascending only)
|
||||||
|
* `likes`: sort by number of likes (ascending only)
|
||||||
* Select the filter to use when browsing the videos on an instance:
|
* Select the filter to use when browsing the videos on an instance:
|
||||||
* local will only display the videos which are local to the selected instance
|
* local will only display the videos which are local to the selected instance
|
||||||
* all-local will only display the videos which are local to the selected
|
* all-local will only display the videos which are local to the selected
|
||||||
|
@ -53,8 +55,10 @@ the add-on with:
|
||||||
|
|
||||||
* This add-on doesn't support Webtorrent yet. So, it cannot download/share
|
* This add-on doesn't support Webtorrent yet. So, it cannot download/share
|
||||||
from/to regular PeerTube clients.
|
from/to regular PeerTube clients.
|
||||||
* The add-on doesn't delete the downloaded files at the moment. So, it may fill
|
* The add-on doesn't delete the downloaded files at the moment so it may fill
|
||||||
up your disk.
|
up your disk. You may delete manually the downloaded files in the folder
|
||||||
|
`<kodi_home>/temp/plugin.video.peertube/` (more information on `<kodi_home>`
|
||||||
|
[here](https://kodi.wiki/view/Kodi_data_folder#Location)).
|
||||||
|
|
||||||
# Installation and prerequisites
|
# Installation and prerequisites
|
||||||
|
|
||||||
|
|
|
@ -161,6 +161,14 @@ class PeerTubeAddon():
|
||||||
data["thumbnailPath"]),
|
data["thumbnailPath"]),
|
||||||
aired=data["publishedAt"]
|
aired=data["publishedAt"]
|
||||||
)
|
)
|
||||||
|
# Note: the type of video (live or not) is available in "response"
|
||||||
|
# but this information is ignored here so that the "play_video"
|
||||||
|
# action is the same whatever the type of the video. The goal is to
|
||||||
|
# allow external users of the API to play a video only with its ID
|
||||||
|
# without knowing its type.
|
||||||
|
# The information about the type of the video will anyway be
|
||||||
|
# available in the response used to get the URL of a video so this
|
||||||
|
# solution does not impact the performance.
|
||||||
|
|
||||||
yield video_info
|
yield video_info
|
||||||
else:
|
else:
|
||||||
|
@ -202,13 +210,18 @@ class PeerTubeAddon():
|
||||||
return next_page_item
|
return next_page_item
|
||||||
|
|
||||||
def _get_video_url(self, video_id, instance=None):
|
def _get_video_url(self, video_id, instance=None):
|
||||||
"""Find the URL of the video with the best possible quality matching
|
"""Return the URL of a video and its type (live or not)
|
||||||
|
|
||||||
|
Find the URL of the video with the best possible quality matching
|
||||||
user's preferences.
|
user's preferences.
|
||||||
|
The information whether the video is live or not will also be returned.
|
||||||
|
|
||||||
:param str video_id: ID of the torrent linked with the video
|
:param str video_id: ID of the torrent linked with the video
|
||||||
:param str instance: PeerTube instance hosting the video (optional)
|
:param str instance: PeerTube instance hosting the video (optional)
|
||||||
:return: URL of the video containing the resolution
|
:return: a boolean indicating if the video is a live stream and the URL
|
||||||
:rtype: str
|
of the video (containing the resolution for non-live videos) as a
|
||||||
|
string
|
||||||
|
:rtype: tuple
|
||||||
"""
|
"""
|
||||||
# Retrieve the information about the video including the different
|
# Retrieve the information about the video including the different
|
||||||
# resolutions available
|
# resolutions available
|
||||||
|
@ -218,16 +231,24 @@ class PeerTubeAddon():
|
||||||
current_resolution = 0
|
current_resolution = 0
|
||||||
higher_resolution = -1
|
higher_resolution = -1
|
||||||
url = ""
|
url = ""
|
||||||
|
is_live = False
|
||||||
for video in video_files:
|
for video in video_files:
|
||||||
# Get the resolution
|
# Get the resolution
|
||||||
resolution = video["resolution"]
|
resolution = video.get("resolution")
|
||||||
|
if resolution is None:
|
||||||
|
# If there is no resolution in the dict, then the video is a
|
||||||
|
# live stream: no need to find the best resolution as there is
|
||||||
|
# only 1 URL in this case
|
||||||
|
url = video["url"]
|
||||||
|
is_live = True
|
||||||
|
return (is_live, url)
|
||||||
if resolution == self.preferred_resolution:
|
if resolution == self.preferred_resolution:
|
||||||
# Stop directly when we find the exact same resolution as the
|
# Stop directly when we find the exact same resolution as the
|
||||||
# user's preferred one
|
# user's preferred one
|
||||||
kodi.debug("Found video with preferred resolution ({})"
|
kodi.debug("Found video with preferred resolution ({})"
|
||||||
.format(self.preferred_resolution))
|
.format(self.preferred_resolution))
|
||||||
url = video["url"]
|
url = video["url"]
|
||||||
break
|
return (is_live, url)
|
||||||
elif (resolution < self.preferred_resolution
|
elif (resolution < self.preferred_resolution
|
||||||
and resolution > current_resolution):
|
and resolution > current_resolution):
|
||||||
# Otherwise, try to find the best one just below the user's
|
# Otherwise, try to find the best one just below the user's
|
||||||
|
@ -255,7 +276,7 @@ class PeerTubeAddon():
|
||||||
.format(higher_resolution))
|
.format(higher_resolution))
|
||||||
url = backup_url
|
url = backup_url
|
||||||
|
|
||||||
return url
|
return (is_live, url)
|
||||||
|
|
||||||
def _home_page(self):
|
def _home_page(self):
|
||||||
"""Display the items of the home page of the add-on"""
|
"""Display the items of the home page of the add-on"""
|
||||||
|
@ -417,11 +438,17 @@ class PeerTubeAddon():
|
||||||
elif action == "play_video":
|
elif action == "play_video":
|
||||||
# This action comes with the id of the video to play as
|
# This action comes with the id of the video to play as
|
||||||
# parameter. The instance may also be in the parameters. Use
|
# parameter. The instance may also be in the parameters. Use
|
||||||
# these parameters to retrieve the complete URL (containing the
|
# these parameters to retrieve the complete URL of the video
|
||||||
# resolution).
|
# (containing the resolution) and the type of the video (live
|
||||||
url = self._get_video_url(instance=params.get("instance"),
|
# or not).
|
||||||
video_id=params.get("id"))
|
is_live, url = self._get_video_url(
|
||||||
# Play the video using the URL
|
instance=params.get("instance"),video_id=params.get("id"))
|
||||||
|
|
||||||
|
# Play the video (Kodi can play live videos (.m3u8) out of the
|
||||||
|
# box whereas torrents must first be downloaded)
|
||||||
|
if is_live:
|
||||||
|
kodi.play(url)
|
||||||
|
else:
|
||||||
self._play_video(url)
|
self._play_video(url)
|
||||||
elif action == "select_instance":
|
elif action == "select_instance":
|
||||||
# Set the selected instance as the preferred instance
|
# Set the selected instance as the preferred instance
|
||||||
|
@ -436,6 +463,6 @@ class PeerTubeAddon():
|
||||||
kodi.open_dialog(
|
kodi.open_dialog(
|
||||||
title="Error: libtorrent could not be imported",
|
title="Error: libtorrent could not be imported",
|
||||||
message="You can still browse and search videos but you"
|
message="You can still browse and search videos but you"
|
||||||
" will not be able to play them.\n"
|
" will not be able to play them (except live"
|
||||||
"Please follow the instructions at {}"
|
" videos).\nPlease follow the instructions at {}"
|
||||||
.format(self.HELP_URL))
|
.format(self.HELP_URL))
|
||||||
|
|
|
@ -121,7 +121,8 @@ class PeerTube:
|
||||||
"""Return the URLs of a video
|
"""Return the URLs of a video
|
||||||
|
|
||||||
PeerTube creates 1 URL for each resolution of a video so this method
|
PeerTube creates 1 URL for each resolution of a video so this method
|
||||||
returns a list of URL/resolution pairs.
|
returns a list of URL/resolution pairs. In the case of a live video,
|
||||||
|
only an URL will be returned (no resolution).
|
||||||
|
|
||||||
:param str video_id: ID or UUID of the video
|
:param str video_id: ID or UUID of the video
|
||||||
:param str instance: URL of the instance hosting the video. The
|
:param str instance: URL of the instance hosting the video. The
|
||||||
|
@ -134,10 +135,18 @@ class PeerTube:
|
||||||
url="videos/{}".format(video_id),
|
url="videos/{}".format(video_id),
|
||||||
instance=instance)
|
instance=instance)
|
||||||
|
|
||||||
# Depending if WebTorrent is enabled or not, the files corresponding to
|
if metadata["isLive"]:
|
||||||
# different resolutions available for a video may be stored in "files"
|
# When the video is a live, yield the unique playlist URL (there is
|
||||||
# or "streamingPlaylists[].files". Note that "files" will always exist
|
# no resolution in this case)
|
||||||
# in the response but may be empty.
|
yield {
|
||||||
|
"url": metadata['streamingPlaylists'][0]['playlistUrl'],
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# For non live videos, the files corresponding to different
|
||||||
|
# resolutions available for a video may be stored in "files" or
|
||||||
|
# "streamingPlaylists[].files" depending if WebTorrent is enabled
|
||||||
|
# or not. Note that "files" will always exist in the response but
|
||||||
|
# may be empty so len() must be used.
|
||||||
if len(metadata["files"]) != 0:
|
if len(metadata["files"]) != 0:
|
||||||
files = metadata["files"]
|
files = metadata["files"]
|
||||||
else:
|
else:
|
||||||
|
@ -147,7 +156,6 @@ class PeerTube:
|
||||||
yield {
|
yield {
|
||||||
"resolution": int(file["resolution"]["id"]),
|
"resolution": int(file["resolution"]["id"]),
|
||||||
"url": file["torrentUrl"],
|
"url": file["torrentUrl"],
|
||||||
"is_live": False
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def list_videos(self, start):
|
def list_videos(self, start):
|
||||||
|
|
Loading…
Reference in New Issue