text: import logging
  import re
  import time
  
  from streamlink.compat import range, urlparse, urlunparse
  from streamlink.exceptions import PluginError, StreamError
  from streamlink.plugin import Plugin, pluginmatcher
  from streamlink.plugin.api import validate
  from streamlink.stream.hls import HLSStream, HLSStreamReader, HLSStreamWorker, Sequence
  from streamlink.stream.hls_playlist import load as load_hls_playlist
  
  log = logging.getLogger(__name__)
  
  
  class FilmOnHLSStreamWorker(HLSStreamWorker):
      def reload_playlist(self):
          if self.closed:
              return
  
          self.reader.buffer.wait_free()
          log.debug("Reloading playlist")
  
          if self.stream.channel:
              parsed = urlparse(self.stream.url)
              if self.stream._first_netloc is None:
                  # save the first netloc
                  self.stream._first_netloc = parsed.netloc
              # always use the first saved netloc
              new_stream_url = parsed._replace(netloc=self.stream._first_netloc).geturl()
          else:
              new_stream_url = self.stream.url
  
          try:
              res = self.session.http.get(
                  new_stream_url,
                  exception=StreamError,
                  retries=self.playlist_reload_retries,
                  **self.reader.request_params)
          except StreamError as err:
              if (hasattr(self.stream, "watch_timeout")
                      and any(x in str(err) for x in ("403 Client Error",
                                                      "502 Server Error"))):
                  self.stream.watch_timeout = 0
                  self.playlist_reload_time = 0
                  log.debug("Force reloading the channel playlist on error: {0}", err)
                  return
              raise err
  
          try:
              playlist = load_hls_playlist(res.text, res.url)
          except ValueError as err:
              raise StreamError(err)
  
          if playlist.is_master:
              raise StreamError("Attempted to play a variant playlist, use "
                                "'hls://{0}' instead".format(self.stream.url))
  
          if playlist.iframes_only:
              raise StreamError("Streams containing I-frames only is not playable")
  
          media_sequence = playlist.media_sequence or 0
          sequences = [Sequence(media_sequence + i, s)
                       for i, s in enumerate(playlist.segments)]
  
          if sequences:
              self.process_sequences(playlist, sequences)
  
  
  class FilmOnHLSStreamReader(HLSStreamReader):
      __worker__ = FilmOnHLSStreamWorker
  
  
  class FilmOnHLS(HLSStream):
      __shortname__ = "hls-filmon"
      __reader__ = FilmOnHLSStreamReader
  
      def __init__(self, session_, channel=None, vod_id=None, quality="high", **args):
          super(FilmOnHLS, self).__init__(session_, None, **args)
          self.channel = channel
          self.vod_id = vod_id
          if self.channel is None and self.vod_id is None:
              raise ValueError("channel or vod_id must be set")
          self.quality = quality
          self.api = FilmOnAPI(session_)
          self._url = None
          self.watch_timeout = 0
          self._first_netloc = None
  
      def _get_stream_data(self):
          if self.channel:
              log.debug("Reloading FilmOn channel playlist: {0}", self.channel)
              data = self.api.channel(self.channel)
              for stream in data["streams"]:
                  yield stream
          elif self.vod_id:
              log.debug("Reloading FilmOn VOD playlist: {0}", self.vod_id)
              data = self.api.vod(self.vod_id)
              for _, stream in data["streams"].items():
                  yield stream
  
      @property
      def url(self):
          # If the watch timeout has passed then refresh the playlist from the API
          if int(time.time()) >= self.watch_timeout:
              for stream in self._get_stream_data():
                  if stream["quality"] == self.quality:
                      self.watch_timeout = int(time.time()) + stream["watch-timeout"]
                      self._url = stream["url"]
                      return self._url
              raise StreamError("cannot refresh FilmOn HLS Stream playlist")
          else:
              return self._url
  
      def to_url(self):
          url = self.url
          expires = self.watch_timeout - time.time()
          if expires < 0:
              raise TypeError("Stream has expired and cannot be converted to a URL")
          return url
  
  
  class FilmOnAPI(object):
      def __init__(self, session):
          self.session = session
  
      channel_url = "http://www.filmon.com/api-v2/channel/{0}?protocol=hls"
      vod_url = "http://www.filmon.com/vod/info/{0}"
  
      stream_schema = {
          "quality": validate.text,
          "url": validate.url(),
          "watch-timeout": int
      }
      api_schema = validate.Schema(
          {
              "data": {
                  "streams": validate.any(
                      {validate.text: stream_schema},
                      [stream_schema]
                  )
              }
          },
          validate.get("data")
      )
  
      def channel(self, channel):
          for _ in range(5):
              if _ > 0:
                  log.debug("channel sleep {0}".format(_))
                  time.sleep(0.75)
  
              # retry for 50X errors
              try:
                  res = self.session.http.get(self.channel_url.format(channel))
                  if res:
                      # retry for invalid response data
                      try:
                          return self.session.http.json(res, schema=self.api_schema)
                      except PluginError:
                          log.debug("invalid or non-JSON data received")
                          continue
              except Exception:
                  log.debug("invalid server response")
  
          raise PluginError("Unable to find 'self.api.channel' for {0}".format(channel))
  
      def vod(self, vod_id):
          res = self.session.http.get(self.vod_url.format(vod_id))
          return self.session.http.json(res, schema=self.api_schema)
  
  
  @pluginmatcher(re.compile(r"""
      https?://(?:www\.)?filmon\.(?:tv|com)/
      (?:
          (?:
              index/popout\?
              |
              (?:tv/)?channel/(?:export\?)?
              |
              tv/(?!channel/)
              |
              channel/
              |
              (?P<is_group>group/)
          )(?:channel_id=)?(?P<channel>[-_\w]+)
          |
          vod/view/(?P<vod_id>\d+)-
      )
  """, re.VERBOSE))
  class Filmon(Plugin):
      _channel_id_re = re.compile(r"""channel_id\s*=\s*(?P<quote>['"]?)(?P<value>\d+)(?P=quote)""")
      _channel_id_schema = validate.Schema(
          validate.transform(_channel_id_re.search),
          validate.any(None, validate.get("value"))
      )
  
      quality_weights = {
          "high": 720,
          "low": 480
      }
  
      TIME_CHANNEL = 60 * 60 * 24 * 365
  
      def __init__(self, url):
          super(Filmon, self).__init__(url)
          parsed = urlparse(self.url)
          if parsed.path.startswith("/channel/"):
              self.url = urlunparse(parsed._replace(path=parsed.path.replace("/channel/", "/tv/")))
          self.api = FilmOnAPI(self.session)
  
      @classmethod
      def stream_weight(cls, key):
          weight = cls.quality_weights.get(key)
          if weight:
              return weight, "filmon"
  
          return Plugin.stream_weight(key)
  
      def _get_streams(self):
          channel = self.match.group("channel")
          vod_id = self.match.group("vod_id")
          is_group = self.match.group("is_group")
  
          if vod_id:
              data = self.api.vod(vod_id)
              for _, stream in data["streams"].items():
                  streams = HLSStream.parse_variant_playlist(self.session, stream["url"])
                  if not streams:
                      yield stream["quality"], HLSStream(self.session, stream["url"])
                  else:
                      for s in streams.items():
                          yield s
          else:
              if channel and not channel.isdigit():
                  _id = self.cache.get(channel)
                  if _id is None:
                      _id = self.session.http.get(self.url, schema=self._channel_id_schema)
                      log.debug("Found channel ID: {0}", _id)
                      # do not cache a group url
                      if _id and not is_group:
                          self.cache.set(channel, _id, expires=self.TIME_CHANNEL)
                  else:
                      log.debug("Found cached channel ID: {0}", _id)
              else:
                  _id = channel
  
              if _id is None:
                  raise PluginError("Unable to find channel ID: {0}".format(channel))
  
              try:
                  data = self.api.channel(_id)
                  for stream in data["streams"]:
                      yield stream["quality"], FilmOnHLS(self.session, channel=_id, quality=stream["quality"])
              except Exception:
                  if channel and not channel.isdigit():
                      self.cache.set(channel, None, expires=0)
                      log.debug("Reset cached channel: {0}", channel)
  
                  raise
  
  
  __plugin__ = Filmon
  