Repository: streamlink/streamlink
Pull request #2866 (2020-04-06 16:02:01)

plugins.tv360: updated URL and HLS stream extraction method


Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 3114eed5..a9516b02 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -32,16 +32,16 @@ jobs:
         with:
           python-version: ${{ matrix.python }}
       - name: Install dependencies
-        continue-on-error: ${{ matrix.continue }}
+        continue-on-error: ${{ matrix.continue || false }}
         run: bash ./script/install-dependencies.sh
       - name: Test
-        continue-on-error: ${{ matrix.continue }}
+        continue-on-error: ${{ matrix.continue || false }}
         run: pytest -r a --cov --cov-report=xml
       - name: Lint
-        continue-on-error: ${{ matrix.continue }}
+        continue-on-error: ${{ matrix.continue || false }}
         run: flake8
       - name: Upload coverage data
-        continue-on-error: ${{ matrix.continue }}
+        continue-on-error: ${{ matrix.continue || false }}
         uses: codecov/codecov-action@v1
 
   documentation:
diff --git a/docs/_themes/sphinx_rtd_theme_violet/static/css/theme.css b/docs/_themes/sphinx_rtd_theme_violet/static/css/theme.css
index 26763c0c..86f10b22 100644
--- a/docs/_themes/sphinx_rtd_theme_violet/static/css/theme.css
+++ b/docs/_themes/sphinx_rtd_theme_violet/static/css/theme.css
@@ -4826,3 +4826,14 @@ table.table-custom-layout tbody tr td {
   overflow: unset;
   white-space: unset;
 }
+
+/* github avatar pictures on the donate page */
+.github-avatar {
+  float: left;
+  width: 150px;
+  height: 150px;
+  margin: 0 1rem 1rem 0;
+}
+.github-avatar + .container > ul > li {
+  list-style: none;
+}
diff --git a/docs/api_guide.rst b/docs/api_guide.rst
index 70efcdb6..797b685f 100644
--- a/docs/api_guide.rst
+++ b/docs/api_guide.rst
@@ -3,8 +3,6 @@
 API Guide
 =========
 
-.. module:: streamlink
-
 This API is what powers the :ref:`cli` but is also available to developers that wish
 to make use of the data Streamlink can retrieve in their own application.
 
diff --git a/docs/donate.rst b/docs/donate.rst
index 575852c7..326a4ad2 100644
--- a/docs/donate.rst
+++ b/docs/donate.rst
@@ -1,40 +1,64 @@
 Donating
 --------
 
-Thank you for considering a donation to the Streamlink team! Streamlink is an
-organization composed of a geographically diverse group of individuals.
-We accept donations/tips in a variety of ways. If you would like to donate
-directly to a specific user, please review their details in the team section
-below. If you would prefer to donate to the team as a whole, please use our
-`Open Collective <https://opencollective.com/streamlink>`_. Note that
-donations are not tax deductible as Streamlink does not operate as a charitable
-organization. Your donation to the team or a team member may be used in any
-way and does not come with expectations of work to be provided or as payment
-for future work.
-
----------------
-Streamlink Team
----------------
-
-Bastimeyer 
+Thank you for considering a donation to the Streamlink team!
+
+Streamlink is an organization composed of a geographically diverse group of
+individuals. Donations/tips can be sent in a variety of ways. If you would like
+to donate directly to a specific user, please review their details in the
+team section below.
+
+If you would prefer to donate to the team as a whole, please use our
+`Open Collective <https://opencollective.com/streamlink>`_.
+
+Note that donations are not tax deductible, as Streamlink does not operate as
+a charitable organization. Your donation to the team or a team member may be
+used in any way and does not come with expectations of work to be provided or
+as payment for future work.
+
+.. raw:: html
+
+   <h1>Individual team member donations</h1>
+
+Bastimeyer
 ^^^^^^^^^^
 
-- `Github <https://github.com/bastimeyer>`__
-- `Bitcoin <https://blockchain.info/qr?data=1EZg8eBz4RdPb8pEzYD9JEzr9Fyitzj8j8>`__ (`1EZg8eBz4RdPb8pEzYD9JEzr9Fyitzj8j8`)
-- `Dogecoin <https://blockchain.info/qr?data=DGkVP7AMUHr2TbVnWy6QyZqSMvCkeEo1ab>`__ (`DGkVP7AMUHr2TbVnWy6QyZqSMvCkeEo1ab`)
-- `Flattr <https://flattr.com/thing/3956088>`__
-- `Paypal <https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YUCGRLVALHS8C&item_name=Streamlink%20Twitch%20GUI>`__
+.. container:: clearfix
+
+   .. image:: https://github.com/bastimeyer.png?size=150
+      :class: github-avatar
+
+   .. container::
 
-Beardypig 
+      - `Github <https://github.com/bastimeyer>`__
+      - `Github Sponsors <https://github.com/sponsors/bastimeyer>`__
+      - `Paypal <https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YUCGRLVALHS8C&item_name=Streamlink%20Twitch%20GUI>`__
+      - `Bitcoin <https://blockchain.info/qr?data=1EZg8eBz4RdPb8pEzYD9JEzr9Fyitzj8j8>`__ :code:`1EZg8eBz4RdPb8pEzYD9JEzr9Fyitzj8j8`
+
+Beardypig
 ^^^^^^^^^
 
-- `Github <https://github.com/beardypig>`__
-- `Bitcoin <https://blockchain.info/qr?data=1Ey3KZ9SwTj6QyASE6vgJVebUiJsW1HuRh>`__ (`1Ey3KZ9SwTj6QyASE6vgJVebUiJsW1HuRh`)
+.. container:: clearfix
+
+   .. image:: https://github.com/beardypig.png?size=150
+      :class: github-avatar
+
+   .. container::
+
+      - `Github <https://github.com/beardypig>`__
+      - `Bitcoin <https://blockchain.info/qr?data=1Ey3KZ9SwTj6QyASE6vgJVebUiJsW1HuRh>`__ :code:`1Ey3KZ9SwTj6QyASE6vgJVebUiJsW1HuRh`
 
 Gravyboat
 ^^^^^^^^^
 
-- `Github <https://github.com/gravyboat>`__
-- `Bitcoin <https://blockchain.info/qr?data=1PpdFh9LkTsjtG2AAcrWqk6RiFrC2iTCxj>`__ (`1PpdFh9LkTsjtG2AAcrWqk6RiFrC2iTCxj`)
-- `Flattr <https://flattr.com/@gravyboat>`__
-- `Gratipay <https://gratipay.com/~gravyboat/>`__
+.. container:: clearfix
+
+   .. image:: https://github.com/gravyboat.png?size=150
+      :class: github-avatar
+
+   .. container::
+
+      - `Github <https://github.com/gravyboat>`__
+      - `Bitcoin <https://blockchain.info/qr?data=1PpdFh9LkTsjtG2AAcrWqk6RiFrC2iTCxj>`__ :code:`1PpdFh9LkTsjtG2AAcrWqk6RiFrC2iTCxj`
+      - `Flattr <https://flattr.com/@gravyboat>`__
+      - `Gratipay <https://gratipay.com/~gravyboat/>`__
diff --git a/docs/index.rst b/docs/index.rst
index fd774572..2d2fa53a 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,32 +1,36 @@
 Overview
 --------
 
-Streamlink is a :ref:`command-line utility <cli>` that pipes video streams
+Streamlink is a :ref:`command-line utility <cli>` which pipes video streams
 from various services into a video player, such as `VLC`_.
-The main purpose of Streamlink is to allow the user to avoid buggy and CPU
-heavy flash plugins but still be able to enjoy various streamed content.
+The main purpose of Streamlink is to avoid resource-heavy and unoptimized websites,
+while still allowing the user to enjoy various streamed content.
 There is also an :ref:`API <api_guide>` available for developers who want access
-to the video stream data. This project was forked from Livestreamer, which is
-no longer maintained.
+to the stream data.
 
-- Latest release: |version| (https://github.com/streamlink/streamlink/releases/latest)
-- GitHub: https://github.com/streamlink/streamlink
-- Issue tracker: https://github.com/streamlink/streamlink/issues
-- PyPI: https://pypi.org/project/streamlink/
-- Free software: Simplified BSD license
+This project was forked from Livestreamer, which is no longer maintained.
+
+Latest release (|version|)
+    https://github.com/streamlink/streamlink/releases/latest
+GitHub
+    https://github.com/streamlink/streamlink
+Issue tracker
+    https://github.com/streamlink/streamlink/issues
+PyPI
+    https://pypi.org/project/streamlink/
+Free software
+    Simplified BSD license
 
 Features
 --------
 
 Streamlink is built upon a plugin system which allows support for new services
-to be easily added. Currently most of the big streaming services are supported,
-such as:
+to be easily added. Most of the big streaming services are supported, such as:
 
-- `Dailymotion <https://www.dailymotion.com/live>`_
-- `Livestream <https://livestream.com/>`_
-- `Twitch <https://www.twitch.tv/>`_
-- `UStream <http://www.ustream.tv/explore/all>`_
-- `YouTube <https://www.youtube.com/>`_
+- `Twitch.tv <https://www.twitch.tv/>`_
+- `YouTube.com <https://www.youtube.com/>`_
+- `Livestream.com <https://livestream.com/>`_
+- `Dailymotion.com <https://www.dailymotion.com/live>`_
 
 ... and many more. A full list of plugins currently included can be found
 on the :ref:`plugin_matrix` page.
@@ -34,18 +38,17 @@ on the :ref:`plugin_matrix` page.
 Quickstart
 ----------
 
-The default behaviour of Streamlink is to playback a stream in the default
-player (`VLC <https://www.videolan.org/>`_).
+The default behavior of Streamlink is to play back streams in the `VLC <https://www.videolan.org/>`_ player.
 
 .. sourcecode:: console
 
-    # pip install streamlink
     $ streamlink twitch.tv/day9tv best
     [cli][info] Found matching plugin twitch for URL twitch.tv/day9tv
-    [cli][info] Opening stream: source (hls)
+    [cli][info] Available streams: audio_only, 160p (worst), 360p, 480p, 720p, 720p60, 1080p60 (best)
+    [cli][info] Opening stream: 1080p60 (hls)
     [cli][info] Starting player: vlc
 
-For more in-depth usage and install instructions see the `User guide`_.
+For more in-depth usage and install instructions, please refer to the `User guide`_.
 
 User guide
 ----------
diff --git a/docs/install.rst b/docs/install.rst
index 97ffe9e9..6c125b2b 100644
--- a/docs/install.rst
+++ b/docs/install.rst
@@ -166,13 +166,15 @@ Windows port. version                RosadinTV <RosadinTV at outlook.com> |br|
 ==================================== ===========================================
 
 
-Source code
------------
+PyPI package and source code
+----------------------------
 
-If a package is not available on your platform (or if it's out of date), Streamlink can be installed from source.
+If a package is not available on your platform, or if it's out of date,
+Streamlink can be installed via `pip`_, the Python package manager.
+
+In addition to using `pip`_, Streamlink can also be installed from source
+via `setuptools`_ after cloning the `git`_ repository.
 
-This can be done in a couple of different ways, for example by using `pip`_, the Python package manager,
-or by checking out the source code with `git`_ and installing it via setuptools. |br|
 Using :command:`easy_install` is no longer recommended.
 
 .. note::
@@ -230,6 +232,7 @@ Version                              Installing
 ==================================== ===========================================
 
 .. _pip: https://pip.readthedocs.org/en/latest/installing.html
+.. _setuptools: https://setuptools.readthedocs.io/en/latest/
 .. _git: https://git-scm.com/
 .. _Latest release (pip): https://pypi.python.org/pypi/streamlink
 .. _Latest release (easy_install): https://pypi.python.org/pypi/streamlink
@@ -348,16 +351,17 @@ Release                              Notes
 ==================================== ====================================
 `Stable release`_                    Download the installer from the `GitHub releases page`_.
 
-`Development build`_                 For testing purposes only! Updated once per day. |br|
-                                     Download the installer from `Bintray`_. |br|
-                                     See the `list of recent changes`_ since the last stable release.
+`Development build`_                 For testing purposes only! Built each day at midnight (UTC). |br|
+                                     Download the zipped installer from the `build artifacts`_ section of one of
+                                     the recent scheduled builds. Build artifacts are stored by Github for 90 days. |br|
+                                     See the `commit log`_ for a list of changes since the last stable release.
 ==================================== ====================================
 
 .. _Stable release:
 .. _GitHub releases page: https://github.com/streamlink/streamlink/releases/latest
 .. _Development build:
-.. _Bintray: https://bintray.com/streamlink/streamlink-nightly/streamlink/_latestVersion/#files
-.. _list of recent changes: https://bintray.com/streamlink/streamlink-nightly/streamlink/_latestVersion/#release
+.. _build artifacts: https://github.com/streamlink/streamlink/actions?query=event%3Aschedule+is%3Asuccess+branch%3Amaster
+.. _commit log: https://github.com/streamlink/streamlink/commits/master
 
 These installers contain:
 
diff --git a/src/streamlink/plugins/tv360.py b/src/streamlink/plugins/tv360.py
index 215afa81..f35ed6a5 100644
--- a/src/streamlink/plugins/tv360.py
+++ b/src/streamlink/plugins/tv360.py
@@ -1,32 +1,19 @@
 from __future__ import print_function
+
 import re
-from functools import partial
 
 from streamlink.plugin import Plugin
 from streamlink.plugin.api import validate
 from streamlink.stream import HLSStream
-from streamlink.utils import parse_json
 
 
 class TV360(Plugin):
-    url_re = re.compile(r"https?://(?:www.)?tv360.com.tr/CanliYayin")
-    data_re = re.compile(r'''div.*?data-tp=(?P<q>["'])(?P<data>.*?)(?P=q)''', re.DOTALL)
-    _js_to_json = partial(re.compile(r"""(\w+):(["']|\d+,|true|false)""").sub, r'"\1":\2')
-    data_schema = validate.Schema(
-        validate.transform(data_re.search),
-        validate.any(
-            None,
-            validate.all(
-                validate.get("data"),
-                validate.transform(_js_to_json),
-                validate.transform(lambda x: x.replace("'", '"')),
-                validate.transform(parse_json),
-                {
-                    "tp_type": "hls4",
-                    "tp_file": validate.url(),
-                }
-            )
-        )
+    url_re = re.compile(r"https?://(?:www.)?tv360.com.tr/canli-yayin")
+    hls_re = re.compile(r'''hls.loadSource\(["'](http.*m3u8)["']\)''', re.DOTALL)
+
+    hls_schema = validate.Schema(
+        validate.transform(hls_re.search),
+        validate.any(None, validate.all(validate.get(1)))
     )
 
     @classmethod
@@ -35,10 +22,10 @@ class TV360(Plugin):
 
     def _get_streams(self):
         res = self.session.http.get(self.url)
-        data = self.data_schema.validate(res.text)
+        hls_url = self.hls_re.search(res.text)
 
-        if data:
-            return HLSStream.parse_variant_playlist(self.session, data["tp_file"])
+        if hls_url:
+            return HLSStream.parse_variant_playlist(self.session, hls_url.group(1))
 
 
 __plugin__ = TV360


Test patch:
diff --git a/tests/plugins/test_tv360.py b/tests/plugins/test_tv360.py
index 306b62b4..0e87c18b 100644
--- a/tests/plugins/test_tv360.py
+++ b/tests/plugins/test_tv360.py
@@ -6,7 +6,7 @@ from streamlink.plugins.tv360 import TV360
 class TestPluginTV360(unittest.TestCase):
     def test_can_handle_url(self):
         should_match = [
-            'http://tv360.com.tr/CanliYayin',
+            'http://tv360.com.tr/canli-yayin',
         ]
         for url in should_match:
             self.assertTrue(TV360.can_handle_url(url))