mirror of
https://github.com/streamlink/streamlink
synced 2024-11-14 01:04:54 +01:00
Merge pull request #1356 from back-to/mixer
[mixer] replaced beam.pro with mixer.com
This commit is contained in:
commit
e12fae5539
@ -30,7 +30,6 @@ artetv arte.tv Yes Yes
|
||||
atresplayer atresplayer.com Yes No Streams are geo-restricted to Spain.
|
||||
bambuser bambuser.com Yes Yes
|
||||
bbciplayer bbc.co.uk/iplayer Yes Yes Streams may be geo-restricted to the United Kingdom.
|
||||
beam beam.pro Yes Yes
|
||||
beattv be-at.tv Yes Yes Playlist not implemented yet.
|
||||
bfmtv bfmtv.com Yes Yes
|
||||
01net.com
|
||||
@ -131,6 +130,7 @@ media_ccc_de - media.ccc.de Yes Yes Only mp4 and HLS are suppor
|
||||
mediaklikk mediaklikk.hu Yes No Streams may be geo-restricted to Hungary.
|
||||
mips mips.tv Yes -- Requires rtmpdump with K-S-V patches.
|
||||
mitele mitele.es Yes No Streams may be geo-restricted to Spain.
|
||||
mixer mixer.com Yes Yes
|
||||
mlgtv mlg.tv Yes --
|
||||
nbc nbc.com No Yes Streams are geo-restricted to USA. Authentication is not supported.
|
||||
nbcsports nbcsports.com No Yes Streams maybe be geo-restricted to USA. Authentication is not supported.
|
||||
|
@ -1,109 +0,0 @@
|
||||
import re
|
||||
|
||||
from streamlink.compat import urlparse, parse_qsl, urljoin
|
||||
from streamlink.plugin import Plugin
|
||||
from streamlink.plugin.api import http, validate
|
||||
from streamlink.stream import HLSStream
|
||||
from streamlink.stream import HTTPStream
|
||||
from streamlink.stream import RTMPStream
|
||||
|
||||
_url_re = re.compile(r"http(s)?://(\w+.)?beam.pro/(?P<channel>[^/?]+)")
|
||||
|
||||
|
||||
class Beam(Plugin):
|
||||
api_url = "https://beam.pro/api/v1/{type}/{id}"
|
||||
channel_manifest = "https://beam.pro/api/v1/channels/{id}/manifest.{type}"
|
||||
|
||||
_vod_schema = validate.Schema({
|
||||
"state": "AVAILABLE",
|
||||
"vods": [{
|
||||
"baseUrl": validate.url(),
|
||||
"data": validate.any(None, {
|
||||
"Height": int
|
||||
}),
|
||||
"format": validate.text
|
||||
}]
|
||||
},
|
||||
validate.get("vods"),
|
||||
validate.filter(lambda x: x["format"] in ("raw", "hls")),
|
||||
[validate.union({
|
||||
"url": validate.get("baseUrl"),
|
||||
"format": validate.get("format"),
|
||||
"height": validate.all(validate.get("data"), validate.get("Height"))
|
||||
})])
|
||||
_assets_schema = validate.Schema(
|
||||
validate.union({
|
||||
"base": validate.all(
|
||||
validate.xml_find("./head/meta"),
|
||||
validate.get("base"),
|
||||
validate.url(scheme="rtmp")
|
||||
),
|
||||
"videos": validate.all(
|
||||
validate.xml_findall(".//video"),
|
||||
[
|
||||
validate.union({
|
||||
"src": validate.all(
|
||||
validate.get("src"),
|
||||
validate.text
|
||||
),
|
||||
"height": validate.all(
|
||||
validate.get("height"),
|
||||
validate.text,
|
||||
validate.transform(int)
|
||||
)
|
||||
})
|
||||
]
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def can_handle_url(cls, url):
|
||||
return _url_re.match(url)
|
||||
|
||||
def _get_vod_stream(self, vod_id):
|
||||
res = http.get(self.api_url.format(type="recordings", id=vod_id))
|
||||
for sdata in http.json(res, schema=self._vod_schema):
|
||||
if sdata["format"] == "hls":
|
||||
hls_url = urljoin(sdata["url"], "manifest.m3u8")
|
||||
yield "{0}p".format(sdata["height"]), HLSStream(self.session, hls_url)
|
||||
elif sdata["format"] == "raw":
|
||||
raw_url = urljoin(sdata["url"], "source.mp4")
|
||||
yield "{0}p".format(sdata["height"]), HTTPStream(self.session, raw_url)
|
||||
|
||||
def _get_live_stream(self, channel):
|
||||
res = http.get(self.api_url.format(type="channels", id=channel))
|
||||
channel_info = http.json(res)
|
||||
|
||||
if not channel_info["online"]:
|
||||
return
|
||||
|
||||
res = http.get(self.channel_manifest.format(id=channel_info["id"], type="smil"))
|
||||
assets = http.xml(res, schema=self._assets_schema)
|
||||
|
||||
for video in assets["videos"]:
|
||||
name = "{0}p".format(video["height"])
|
||||
stream = RTMPStream(self.session, {
|
||||
"rtmp": "{0}/{1}".format(assets["base"], video["src"])
|
||||
})
|
||||
yield name, stream
|
||||
|
||||
for s in HLSStream.parse_variant_playlist(self.session,
|
||||
self.channel_manifest.format(id=channel_info["id"], type="m3u8")).items():
|
||||
yield s
|
||||
|
||||
def _get_streams(self):
|
||||
params = dict(parse_qsl(urlparse(self.url).query))
|
||||
vod_id = params.get("vod")
|
||||
match = _url_re.match(self.url)
|
||||
channel = match.group("channel")
|
||||
|
||||
if vod_id:
|
||||
self.logger.debug("Looking for VOD {0} from channel: {1}", vod_id, channel)
|
||||
return self._get_vod_stream(vod_id)
|
||||
else:
|
||||
self.logger.debug("Looking for channel: {0}", channel)
|
||||
return self._get_live_stream(channel)
|
||||
|
||||
|
||||
__plugin__ = Beam
|
87
src/streamlink/plugins/mixer.py
Normal file
87
src/streamlink/plugins/mixer.py
Normal file
@ -0,0 +1,87 @@
|
||||
"""
|
||||
Mixer API 1.0 documentation
|
||||
https://dev.mixer.com/rest.html
|
||||
"""
|
||||
import re
|
||||
|
||||
from streamlink import NoStreamsError
|
||||
from streamlink.compat import urlparse, parse_qsl, urljoin
|
||||
from streamlink.plugin import Plugin
|
||||
from streamlink.plugin.api import http, validate
|
||||
from streamlink.stream import HLSStream
|
||||
|
||||
_url_re = re.compile(r"http(s)?://(\w+.)?mixer\.com/(?P<channel>[^/?]+)")
|
||||
|
||||
|
||||
class Mixer(Plugin):
|
||||
api_url = "https://mixer.com/api/v1/{type}/{id}"
|
||||
|
||||
_vod_schema = validate.Schema(
|
||||
{
|
||||
"state": "AVAILABLE",
|
||||
"vods": [{
|
||||
"baseUrl": validate.url(),
|
||||
"data": validate.any(None, {
|
||||
"Height": int
|
||||
}),
|
||||
"format": validate.text
|
||||
}]
|
||||
},
|
||||
validate.get("vods"),
|
||||
validate.filter(lambda x: x["format"] in ("raw", "hls")),
|
||||
[validate.union({
|
||||
"url": validate.get("baseUrl"),
|
||||
"format": validate.get("format"),
|
||||
"height": validate.all(validate.get("data"), validate.get("Height"))
|
||||
})])
|
||||
|
||||
@classmethod
|
||||
def can_handle_url(cls, url):
|
||||
return _url_re.match(url)
|
||||
|
||||
def _get_api_res(self, api_type, api_id):
|
||||
try:
|
||||
res = http.get(self.api_url.format(type=api_type, id=api_id))
|
||||
return res
|
||||
except Exception as e:
|
||||
if "404" in str(e):
|
||||
self.logger.error("invalid {0} - {1}".format(api_type, api_id))
|
||||
elif "429" in str(e):
|
||||
self.logger.error("Too Many Requests, API rate limit exceeded.")
|
||||
raise NoStreamsError(self.url)
|
||||
|
||||
def _get_vod_stream(self, vod_id):
|
||||
res = self._get_api_res("recordings", vod_id)
|
||||
|
||||
for sdata in http.json(res, schema=self._vod_schema):
|
||||
if sdata["format"] == "hls":
|
||||
hls_url = urljoin(sdata["url"], "manifest.m3u8")
|
||||
yield "{0}p".format(sdata["height"]), HLSStream(self.session, hls_url)
|
||||
|
||||
def _get_live_stream(self, channel):
|
||||
res = self._get_api_res("channels", channel)
|
||||
|
||||
channel_info = http.json(res)
|
||||
if not channel_info["online"]:
|
||||
return
|
||||
|
||||
user_id = channel_info["id"]
|
||||
hls_url = self.api_url.format(type="channels", id="{0}/manifest.m3u8".format(user_id))
|
||||
for s in HLSStream.parse_variant_playlist(self.session, hls_url).items():
|
||||
yield s
|
||||
|
||||
def _get_streams(self):
|
||||
params = dict(parse_qsl(urlparse(self.url).query))
|
||||
vod_id = params.get("vod")
|
||||
match = _url_re.match(self.url)
|
||||
channel = match.group("channel")
|
||||
|
||||
if vod_id:
|
||||
self.logger.debug("Looking for VOD {0} from channel: {1}", vod_id, channel)
|
||||
return self._get_vod_stream(vod_id)
|
||||
else:
|
||||
self.logger.debug("Looking for channel: {0}", channel)
|
||||
return self._get_live_stream(channel)
|
||||
|
||||
|
||||
__plugin__ = Mixer
|
15
tests/test_plugin_mixer.py
Normal file
15
tests/test_plugin_mixer.py
Normal file
@ -0,0 +1,15 @@
|
||||
import unittest
|
||||
|
||||
from streamlink.plugins.mixer import Mixer
|
||||
|
||||
|
||||
class TestPluginMixer(unittest.TestCase):
|
||||
def test_can_handle_url(self):
|
||||
# should match
|
||||
self.assertTrue(Mixer.can_handle_url('https://mixer.com/Minecraft?vod=11250002'))
|
||||
self.assertTrue(Mixer.can_handle_url('https://mixer.com/Minecraft'))
|
||||
self.assertTrue(Mixer.can_handle_url('https://mixer.com/Monstercat?vod=11030270'))
|
||||
self.assertTrue(Mixer.can_handle_url('https://mixer.com/Monstercat'))
|
||||
|
||||
# shouldn't match
|
||||
self.assertFalse(Mixer.can_handle_url('https://mixer.com'))
|
Loading…
Reference in New Issue
Block a user