files: """High-level sound and video player."""
  from __future__ import print_function
  from __future__ import division
  from builtins import object
  # ----------------------------------------------------------------------------
  # pyglet
  # Copyright (c) 2006-2008 Alex Holkner
  # All rights reserved.
  #
  # Redistribution and use in source and binary forms, with or without
  # modification, are permitted provided that the following conditions
  # are met:
  #
  #  * Redistributions of source code must retain the above copyright
  #    notice, this list of conditions and the following disclaimer.
  #  * Redistributions in binary form must reproduce the above copyright
  #    notice, this list of conditions and the following disclaimer in
  #    the documentation and/or other materials provided with the
  #    distribution.
  #  * Neither the name of pyglet nor the names of its
  #    contributors may be used to endorse or promote products
  #    derived from this software without specific prior written
  #    permission.
  #
  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  # POSSIBILITY OF SUCH DAMAGE.
  # ----------------------------------------------------------------------------
  
  from collections import deque
  
  import pyglet
  from pyglet.media import buffered_logger as bl
  from pyglet.media.drivers import get_audio_driver
  from pyglet.media.codecs.base import Source
  
  # import cProfile
  
  _debug = pyglet.options['debug_media']
  
  clock = pyglet.clock.get_default()
  
  
  class MasterClock(object):
      """Master clock object.
  
      This is a simple clock object which tracks the time elapsed. It can be
      paused and reset.
      """
  
      def __init__(self):
          """Initialize the clock with time 0."""
          self._time = 0.0
          self._systime = None
  
      def play(self):
          """Start the clock."""
          self._systime = clock.time()
  
      def pause(self):
          """Pause the clock."""
          self._time = self.get_time()
          self._systime = None
  
      def reset(self):
          """Reset the clock to 0."""
          self._time = 0.0
          if self._systime is not None:
              self._systime = clock.time()
  
      def get_time(self):
          """Current master clock time."""
          if self._systime is None:
              now = self._time
          else:
              now = clock.time() - self._systime + self._time
          return now
  
      def set_time(self, value):
          """
          Set the master clock time.
  
          Args:
              value (float): The new :class:`~pyglet.media.player.MasterClock`
                  time.
          """
          self.reset()
          self._time = value
  
  
  class _PlayerProperty(object):
      """Descriptor for Player attributes to forward to the AudioPlayer.
  
      We want the Player to have attributes like volume, pitch, etc. These are
      actually implemented by the AudioPlayer. So this descriptor will forward
      an assignement to one of the attributes to the AudioPlayer. For example
      `player.volume = 0.5` will call `player._audio_player.set_volume(0.5)`.
  
      The Player class has default values at the class level which are retrieved
      if not found on the instance.
      """
  
      def __init__(self, attribute, doc=None):
          self.attribute = attribute
          self.__doc__ = doc or ''
  
      def __get__(self, obj, objtype=None):
          if obj is None:
              return self
          if '_' + self.attribute in obj.__dict__:
              return obj.__dict__['_' + self.attribute]
          return getattr(objtype, '_' + self.attribute)
  
      def __set__(self, obj, value):
          obj.__dict__['_' + self.attribute] = value
          if obj._audio_player:
              getattr(obj._audio_player, 'set_' + self.attribute)(value)
  
  
  class Player(pyglet.event.EventDispatcher):
      """High-level sound and video player."""
  
      # Spacialisation attributes, preserved between audio players
      _volume = 1.0
      _min_distance = 1.0
      _max_distance = 100000000.
  
      _position = (0, 0, 0)
      _pitch = 1.0
  
      _cone_orientation = (0, 0, 1)
      _cone_inner_angle = 360.
      _cone_outer_angle = 360.
      _cone_outer_gain = 1.
  
      def __init__(self):
          """Initialize the Player with a MasterClock."""
          self._source = None
          self._playlists = deque()
          self._audio_player = None
  
          self._texture = None
          # Desired play state (not an indication of actual state).
          self._playing = False
  
          self._mclock = MasterClock()
          #: Loop the current source indefinitely or until
          #: :meth:`~Player.next_source` is called. Defaults to ``False``.
          #:
          #: :type: bool
          #:
          #: .. versionadded:: 1.4
          self.loop = False
  
          # self.pr = cProfile.Profile()
  
      def __del__(self):
          """Release the Player resources."""
          self.delete()
  
      def queue(self, source):
          """
          Queue the source on this player.
  
          If the player has no source, the player will start to play immediately
          or pause depending on its :attr:`.playing` attribute.
  
          Args:
              source (Source or Iterable[Source]): The source to queue.
          """
          if isinstance(source, Source):
              source = _one_item_playlist(source)
          else:
              try:
                  source = iter(source)
              except TypeError:
                  raise TypeError("source must be either a Source or an iterable."
                                  " Received type {0}".format(type(source)))
          self._playlists.append(source)
  
          if self.source is None:
              source = next(self._playlists[0])
              self._source = source._get_queue_source()
  
          self._set_playing(self._playing)
  
      def _set_playing(self, playing):
          # stopping = self._playing and not playing
          # starting = not self._playing and playing
  
          self._playing = playing
          source = self.source
  
          if playing and source:
              if source.audio_format:
                  if self._audio_player is None:
                      self._create_audio_player()
                  if self._audio_player:
                      # We succesfully created an audio player
                      self._audio_player.prefill_audio()
  
              if bl.logger is not None:
                  bl.logger.init_wall_time()
                  bl.logger.log("p.P._sp", 0.0)
  
              if source.video_format:
                  if not self._texture:
                      self._create_texture()
  
              if self._audio_player:
                  self._audio_player.play()
              if source.video_format:
                  pyglet.clock.schedule_once(self.update_texture, 0)
              # For audio synchronization tests, the following will
              # add a delay to de-synchronize the audio.
              # Negative number means audio runs ahead.
              # self._mclock._systime += -0.3
              self._mclock.play()
              if self._audio_player is None and source.video_format is None:
                  pyglet.clock.schedule_once(
                      lambda dt: self.dispatch_event("on_eos"),
                      source.duration,
                  )
  
          else:
              if self._audio_player:
                  self._audio_player.stop()
  
              pyglet.clock.unschedule(self.update_texture)
              self._mclock.pause()
  
      @property
      def playing(self):
          """
          bool: Read-only. Determine if the player state is playing.
  
          The *playing* property is irrespective of whether or not there is
          actually a source to play. If *playing* is ``True`` and a source is
          queued, it will begin to play immediately. If *playing* is ``False``,
          it is implied that the player is paused. There is no other possible
          state.
          """
          return self._playing
  
      def play(self):
          """
          Begin playing the current source.
  
          This has no effect if the player is already playing.
          """
          self._set_playing(True)
  
      def pause(self):
          """
          Pause playback of the current source.
  
          This has no effect if the player is already paused.
          """
          self._set_playing(False)
  
      def delete(self):
          """Release the resources acquired by this player.
  
          The internal audio player and the texture will be deleted.
          """
          if self._audio_player:
              self._audio_player.delete()
              self._audio_player = None
          if self._texture:
              self._texture = None
  
      def next_source(self):
          """
          Move immediately to the next source in the current playlist.
  
          If the playlist is emtpy, discard it and check if another playlist
          is queued.
          There may be a gap in playback while the audio buffer is refilled.
          """
          was_playing = self._playing
          self.pause()
          self._mclock.reset()
  
          if self.source:
              # Reset source to the beginning
              self.seek(0.0)
              self.source.is_player_source = False
  
          playlists = self._playlists
          if not playlists:
              return
  
          try:
              source = next(playlists[0])
          except StopIteration:
              self._playlists.popleft()
              if not self._playlists:
                  source = None
              else:
                  # Could someone queue an iterator which is empty??
                  source = next(self._playlists[0])
  
          if source is None:
              self._source = None
              self.delete()
              self.dispatch_event('on_player_eos')
          else:
              old_audio_format = self.source.audio_format
              old_video_format = self.source.video_format
              self._source = source._get_queue_source()
  
              if old_audio_format == self.source.audio_format:
                  self._audio_player.clear()
                  self._audio_player.source = self.source
              else:
                  self._audio_player.delete()
                  self._audio_player = None
              if old_video_format != self.source.video_format:
                  self._texture = None
                  pyglet.clock.unschedule(self.update_texture)
  
              self._set_playing(was_playing)
              self.dispatch_event('on_player_next_source')
  
      def seek(self, time):
          """
          Seek for playback to the indicated timestamp on the current source.
  
          Timestamp is expressed in seconds. If the timestamp is outside the
          duration of the source, it will be clamped to the end.
  
          Args:
              time (float): The time where to seek in the source, clamped to the
                  beginning and end of the source.
          """
          playing = self._playing
          if playing:
              self.pause()
          if not self.source:
              return
  
          if bl.logger is not None:
              bl.logger.log("p.P.sk", time)
  
          self._mclock.set_time(time)
          self.source.seek(time)
          if self._audio_player:
              # XXX: According to docstring in AbstractAudioPlayer this cannot
              # be called when the player is not stopped
              self._audio_player.clear()
          if self.source.video_format:
              self.update_texture()
              pyglet.clock.unschedule(self.update_texture)
          self._set_playing(playing)
  
      def _create_audio_player(self):
          assert not self._audio_player
          assert self.source
  
          source = self.source
          audio_driver = get_audio_driver()
          if audio_driver is None:
              # Failed to find a valid audio driver
              return
  
          self._audio_player = audio_driver.create_audio_player(source, self)
  
          # Set the audio player attributes
          for attr in ('volume', 'min_distance', 'max_distance', 'position',
                       'pitch', 'cone_orientation', 'cone_inner_angle',
                       'cone_outer_angle', 'cone_outer_gain'):
              value = getattr(self, attr)
              setattr(self, attr, value)
  
      @property
      def source(self):
          """Source: Read-only. The current :class:`Source`, or ``None``."""
          return self._source
  
      @property
      def time(self):
          """
          float: Read-only. Current playback time of the current source.
  
          The playback time is a float expressed in seconds, with 0.0 being the
          beginning of the media. The playback time returned represents the
          player master clock time which is used to synchronize both the audio
          and the video.
          """
          return self._mclock.get_time()
  
      def _create_texture(self):
          video_format = self.source.video_format
          self._texture = pyglet.image.Texture.create(
              video_format.width, video_format.height, rectangle=True)
          self._texture = self._texture.get_transform(flip_y=True)
          # After flipping the texture along the y axis, the anchor_y is set
          # to the top of the image. We want to keep it at the bottom.
          self._texture.anchor_y = 0
          return self._texture
  
      @property
      def texture(self):
          """
          :class:`pyglet.image.Texture`: Get the texture for the current video frame.
  
          You should call this method every time you display a frame of video,
          as multiple textures might be used. The return value will be None if
          there is no video in the current source.
          """
          return self._texture
  
      def get_texture(self):
          """
          Get the texture for the current video frame.
  
          You should call this method every time you display a frame of video,
          as multiple textures might be used. The return value will be None if
          there is no video in the current source.
  
          Returns:
              :class:`pyglet.image.Texture`
  
          .. deprecated:: 1.4
                  Use :attr:`~texture` instead
          """
          return self.texture
  
      def seek_next_frame(self):
          """Step forwards one video frame in the current source."""
          time = self.source.get_next_video_timestamp()
          if time is None:
              return
          self.seek(time)
  
      def update_texture(self, dt=None):
          """Manually update the texture from the current source.
  
          This happens automatically, so you shouldn't need to call this method.
  
          Args:
              dt (float): The time elapsed since the last call to
                  ``update_texture``.
          """
          # self.pr.disable()
          # if dt > 0.05:
          #     print("update_texture dt:", dt)
          #     import pstats
          #     ps = pstats.Stats(self.pr).sort_stats("cumulative")
          #     ps.print_stats()
          source = self.source
          time = self.time
          if bl.logger is not None:
              bl.logger.log(
                  "p.P.ut.1.0", dt, time,
                  self._audio_player.get_time() if self._audio_player else 0,
                  bl.logger.rebased_wall_time()
              )
  
          frame_rate = source.video_format.frame_rate
          frame_duration = 1 / frame_rate
          ts = source.get_next_video_timestamp()
          # Allow up to frame_duration difference
          while ts is not None and ts + frame_duration < time:
              source.get_next_video_frame()  # Discard frame
              if bl.logger is not None:
                  bl.logger.log("p.P.ut.1.5", ts)
              ts = source.get_next_video_timestamp()
  
          if bl.logger is not None:
              bl.logger.log("p.P.ut.1.6", ts)
  
          if ts is None:
              # No more video frames to show. End of video stream.
              if bl.logger is not None:
                  bl.logger.log("p.P.ut.1.7", frame_duration)
  
              pyglet.clock.schedule_once(self._video_finished, 0)
              return
  
          image = source.get_next_video_frame()
          if image is not None:
              if self._texture is None:
                  self._create_texture()
              self._texture.blit_into(image, 0, 0, 0)
          elif bl.logger is not None:
              bl.logger.log("p.P.ut.1.8")
  
          ts = source.get_next_video_timestamp()
          if ts is None:
              delay = frame_duration
          else:
              delay = ts - time
  
          delay = max(0.0, delay)
          if bl.logger is not None:
              bl.logger.log("p.P.ut.1.9", delay, ts)
          pyglet.clock.schedule_once(self.update_texture, delay)
          # self.pr.enable()
  
      def _video_finished(self, dt):
          if self._audio_player is None:
              self.dispatch_event("on_eos")
  
      volume = _PlayerProperty('volume', doc="""
      The volume level of sound playback.
  
      The nominal level is 1.0, and 0.0 is silence.
  
      The volume level is affected by the distance from the listener (if
      positioned).
      """)
      min_distance = _PlayerProperty('min_distance', doc="""
      The distance beyond which the sound volume drops by half, and within
      which no attenuation is applied.
  
      The minimum distance controls how quickly a sound is attenuated as it
      moves away from the listener. The gain is clamped at the nominal value
      within the min distance. By default the value is 1.0.
  
      The unit defaults to meters, but can be modified with the listener
      properties. """)
      max_distance = _PlayerProperty('max_distance', doc="""
      The distance at which no further attenuation is applied.
  
      When the distance from the listener to the player is greater than this
      value, attenuation is calculated as if the distance were value. By
      default the maximum distance is infinity.
  
      The unit defaults to meters, but can be modified with the listener
      properties.
      """)
      position = _PlayerProperty('position', doc="""
      The position of the sound in 3D space.
  
      The position is given as a tuple of floats (x, y, z). The unit
      defaults to meters, but can be modified with the listener properties.
      """)
      pitch = _PlayerProperty('pitch', doc="""
      The pitch shift to apply to the sound.
  
      The nominal pitch is 1.0. A pitch of 2.0 will sound one octave higher,
      and play twice as fast. A pitch of 0.5 will sound one octave lower, and
      play twice as slow. A pitch of 0.0 is not permitted.
      """)
      cone_orientation = _PlayerProperty('cone_orientation', doc="""
      The direction of the sound in 3D space.
  
      The direction is specified as a tuple of floats (x, y, z), and has no
      unit. The default direction is (0, 0, -1). Directional effects are only
      noticeable if the other cone properties are changed from their default
      values.
      """)
      cone_inner_angle = _PlayerProperty('cone_inner_angle', doc="""
      The interior angle of the inner cone.
  
      The angle is given in degrees, and defaults to 360. When the listener
      is positioned within the volume defined by the inner cone, the sound is
      played at normal gain (see :attr:`volume`).
      """)
      cone_outer_angle = _PlayerProperty('cone_outer_angle', doc="""
      The interior angle of the outer cone.
  
      The angle is given in degrees, and defaults to 360. When the listener
      is positioned within the volume defined by the outer cone, but outside
      the volume defined by the inner cone, the gain applied is a smooth
      interpolation between :attr:`volume` and :attr:`cone_outer_gain`.
      """)
      cone_outer_gain = _PlayerProperty('cone_outer_gain', doc="""
      The gain applied outside the cone.
  
      When the listener is positioned outside the volume defined by the outer
      cone, this gain is applied instead of :attr:`volume`.
      """)
  
      # Events
  
      def on_player_eos(self):
          """The player ran out of sources. The playlist is empty.
  
          :event:
          """
          if _debug:
              print('Player.on_player_eos')
  
      def on_eos(self):
          """The current source ran out of data.
  
          The default behaviour is to advance to the next source in the
          playlist if the :attr:`.loop` attribute is set to ``False``.
          If :attr:`.loop` attribute is set to ``True``, the current source
          will start to play again until :meth:`next_source` is called or
          :attr:`.loop` is set to ``False``.
  
          :event:
          """
          if _debug:
              print('Player.on_eos')
          if bl.logger is not None:
              bl.logger.log("p.P.oe")
              bl.logger.close()
  
          if self.loop:
              was_playing = self._playing
              self.pause()
              self._mclock.reset()
  
              if self.source:
                  # Reset source to the beginning
                  self.seek(0.0)
              self._audio_player.clear()
              self._set_playing(was_playing)
  
          else:
              self.next_source()
  
      def on_player_next_source(self):
          """The player starts to play the next queued source in the playlist.
  
          This is a useful event for adjusting the window size to the new
          source :class:`VideoFormat` for example.
  
          :event:
          """
          pass
  
  
  Player.register_event_type('on_eos')
  Player.register_event_type('on_player_eos')
  Player.register_event_type('on_player_next_source')
  
  
  def _one_item_playlist(source):
      yield source
  
  
  class PlayerGroup(object):
      """Group of players that can be played and paused simultaneously.
  
      Create a player group for the given list of players.
  
      All players in the group must currently not belong to any other group.
  
      Args:
          players (List[Player]): List of :class:`.Player` s in this group.
      """
  
      def __init__(self, players):
          """Initialize the PlayerGroup with the players."""
          self.players = list(players)
  
      def play(self):
          """Begin playing all players in the group simultaneously."""
          audio_players = [p._audio_player
                           for p in self.players if p._audio_player]
          if audio_players:
              audio_players[0]._play_group(audio_players)
          for player in self.players:
              player.play()
  
      def pause(self):
          """Pause all players in the group simultaneously."""
          audio_players = [p._audio_player
                           for p in self.players if p._audio_player]
          if audio_players:
              audio_players[0]._stop_group(audio_players)
          for player in self.players:
              player.pause()
  
files: pyglet/media/player.py
files: """High-level sound and video player."""
  from __future__ import print_function
  from __future__ import division
  from builtins import object
  # ----------------------------------------------------------------------------
  # pyglet
  # Copyright (c) 2006-2008 Alex Holkner
  # All rights reserved.
  #
  # Redistribution and use in source and binary forms, with or without
  # modification, are permitted provided that the following conditions
  # are met:
  #
  #  * Redistributions of source code must retain the above copyright
  #    notice, this list of conditions and the following disclaimer.
  #  * Redistributions in binary form must reproduce the above copyright
  #    notice, this list of conditions and the following disclaimer in
  #    the documentation and/or other materials provided with the
  #    distribution.
  #  * Neither the name of pyglet nor the names of its
  #    contributors may be used to endorse or promote products
  #    derived from this software without specific prior written
  #    permission.
  #
  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  # POSSIBILITY OF SUCH DAMAGE.
  # ----------------------------------------------------------------------------
  
  from collections import deque
  
  import pyglet
  from pyglet.media import buffered_logger as bl
  from pyglet.media.drivers import get_audio_driver
  from pyglet.media.codecs.base import Source
  
  # import cProfile
  
  _debug = pyglet.options['debug_media']
  
  clock = pyglet.clock.get_default()
  
  
  class MasterClock(object):
      """Master clock object.
  
      This is a simple clock object which tracks the time elapsed. It can be
      paused and reset.
      """
  
      def __init__(self):
          """Initialize the clock with time 0."""
          self._time = 0.0
          self._systime = None
  
      def play(self):
          """Start the clock."""
          self._systime = clock.time()
  
      def pause(self):
          """Pause the clock."""
          self._time = self.get_time()
          self._systime = None
  
      def reset(self):
          """Reset the clock to 0."""
          self._time = 0.0
          if self._systime is not None:
              self._systime = clock.time()
  
      def get_time(self):
          """Current master clock time."""
          if self._systime is None:
              now = self._time
          else:
              now = clock.time() - self._systime + self._time
          return now
  
      def set_time(self, value):
          """
          Set the master clock time.
  
          Args:
              value (float): The new :class:`~pyglet.media.player.MasterClock`
                  time.
          """
          self.reset()
          self._time = value
  
  
  class _PlayerProperty(object):
      """Descriptor for Player attributes to forward to the AudioPlayer.
  
      We want the Player to have attributes like volume, pitch, etc. These are
      actually implemented by the AudioPlayer. So this descriptor will forward
      an assignement to one of the attributes to the AudioPlayer. For example
      `player.volume = 0.5` will call `player._audio_player.set_volume(0.5)`.
  
      The Player class has default values at the class level which are retrieved
      if not found on the instance.
      """
  
      def __init__(self, attribute, doc=None):
          self.attribute = attribute
          self.__doc__ = doc or ''
  
      def __get__(self, obj, objtype=None):
          if obj is None:
              return self
          if '_' + self.attribute in obj.__dict__:
              return obj.__dict__['_' + self.attribute]
          return getattr(objtype, '_' + self.attribute)
  
      def __set__(self, obj, value):
          obj.__dict__['_' + self.attribute] = value
          if obj._audio_player:
              getattr(obj._audio_player, 'set_' + self.attribute)(value)
  
  
  class Player(pyglet.event.EventDispatcher):
      """High-level sound and video player."""
  
      # Spacialisation attributes, preserved between audio players
      _volume = 1.0
      _min_distance = 1.0
      _max_distance = 100000000.
  
      _position = (0, 0, 0)
      _pitch = 1.0
  
      _cone_orientation = (0, 0, 1)
      _cone_inner_angle = 360.
      _cone_outer_angle = 360.
      _cone_outer_gain = 1.
  
      def __init__(self):
          """Initialize the Player with a MasterClock."""
          self._source = None
          self._playlists = deque()
          self._audio_player = None
  
          self._texture = None
          # Desired play state (not an indication of actual state).
          self._playing = False
  
          self._mclock = MasterClock()
          #: Loop the current source indefinitely or until
          #: :meth:`~Player.next_source` is called. Defaults to ``False``.
          #:
          #: :type: bool
          #:
          #: .. versionadded:: 1.4
          self.loop = False
  
          # self.pr = cProfile.Profile()
  
      def __del__(self):
          """Release the Player resources."""
          self.delete()
  
      def queue(self, source):
          """
          Queue the source on this player.
  
          If the player has no source, the player will start to play immediately
          or pause depending on its :attr:`.playing` attribute.
  
          Args:
              source (Source or Iterable[Source]): The source to queue.
          """
          if isinstance(source, Source):
              source = _one_item_playlist(source)
          else:
              try:
                  source = iter(source)
              except TypeError:
                  raise TypeError("source must be either a Source or an iterable."
                      " Received type {0}".format(type(source)))
          self._playlists.append(source)
  
          if self.source is None:
              source = next(self._playlists[0])
              self._source = source._get_queue_source()
  
          self._set_playing(self._playing)
  
      def _set_playing(self, playing):
          # stopping = self._playing and not playing
          # starting = not self._playing and playing
  
          self._playing = playing
          source = self.source
  
          if playing and source:
              if source.audio_format:
                  if self._audio_player is None:
                      self._create_audio_player()
                  if self._audio_player:
                      # We succesfully created an audio player
                      self._audio_player.prefill_audio()
  
              if bl.logger is not None:
                  bl.logger.init_wall_time()
                  bl.logger.log("p.P._sp", 0.0)
  
              if source.video_format:
                  if not self._texture:
                      self._create_texture()
  
              if self._audio_player:
                  self._audio_player.play()
              if source.video_format:
                  pyglet.clock.schedule_once(self.update_texture, 0)
              # For audio synchronization tests, the following will
              # add a delay to de-synchronize the audio.
              # Negative number means audio runs ahead.
              # self._mclock._systime += -0.3
              self._mclock.play()
          else:
              if self._audio_player:
                  self._audio_player.stop()
  
              pyglet.clock.unschedule(self.update_texture)
              self._mclock.pause()
  
      @property
      def playing(self):
          """
          bool: Read-only. Determine if the player state is playing.
  
          The *playing* property is irrespective of whether or not there is
          actually a source to play. If *playing* is ``True`` and a source is
          queued, it will begin to play immediately. If *playing* is ``False``,
          it is implied that the player is paused. There is no other possible
          state.
          """
          return self._playing
  
      def play(self):
          """
          Begin playing the current source.
  
          This has no effect if the player is already playing.
          """
          self._set_playing(True)
  
      def pause(self):
          """
          Pause playback of the current source.
  
          This has no effect if the player is already paused.
          """
          self._set_playing(False)
  
      def delete(self):
          """Release the resources acquired by this player.
  
          The internal audio player and the texture will be deleted.
          """
          if self._audio_player:
              self._audio_player.delete()
              self._audio_player = None
          if self._texture:
              self._texture = None
  
      def next_source(self):
          """
          Move immediately to the next source in the current playlist.
  
          If the playlist is emtpy, discard it and check if another playlist
          is queued.
          There may be a gap in playback while the audio buffer is refilled.
          """
          was_playing = self._playing
          self.pause()
          self._mclock.reset()
  
          if self.source:
              # Reset source to the beginning
              self.seek(0.0)
              self.source.is_player_source = False
  
          playlists = self._playlists
          if not playlists:
              return
  
          try:
              source = next(playlists[0])
          except StopIteration:
              self._playlists.popleft()
              if not self._playlists:
                  source = None
              else:
                  # Could someone queue an iterator which is empty??
                  source = next(self._playlists[0])
  
          if source is None:
              self._source = None
              self.delete()
              self.dispatch_event('on_player_eos')
          else:
              old_audio_format = self.source.audio_format
              old_video_format = self.source.video_format
              self._source = source._get_queue_source()
  
              if old_audio_format == self.source.audio_format:
                  self._audio_player.clear()
                  self._audio_player.source = self.source
              else:
                  self._audio_player.delete()
                  self._audio_player = None
              if old_video_format != self.source.video_format:
                  self._texture = None
                  pyglet.clock.unschedule(self.update_texture)
  
              self._set_playing(was_playing)
              self.dispatch_event('on_player_next_source')
  
      def seek(self, time):
          """
          Seek for playback to the indicated timestamp on the current source.
  
          Timestamp is expressed in seconds. If the timestamp is outside the
          duration of the source, it will be clamped to the end.
  
          Args:
              time (float): The time where to seek in the source, clamped to the
                  beginning and end of the source.
          """
          playing = self._playing
          if playing:
              self.pause()
          if not self.source:
              return
  
          if bl.logger is not None:
              bl.logger.log("p.P.sk", time)
  
          self._mclock.set_time(time)
          self.source.seek(time)
          if self._audio_player:
              # XXX: According to docstring in AbstractAudioPlayer this cannot
              # be called when the player is not stopped
              self._audio_player.clear()
          if self.source.video_format:
              self.update_texture()
              pyglet.clock.unschedule(self.update_texture)
          self._set_playing(playing)
  
      def _create_audio_player(self):
          assert not self._audio_player
          assert self.source
  
          source = self.source
          audio_driver = get_audio_driver()
          if audio_driver is None:
              # Failed to find a valid audio driver
              self.source.audio_format = None
              return
  
          self._audio_player = audio_driver.create_audio_player(source, self)
  
          # Set the audio player attributes
          for attr in ('volume', 'min_distance', 'max_distance', 'position',
                       'pitch', 'cone_orientation', 'cone_inner_angle',
                       'cone_outer_angle', 'cone_outer_gain'):
              value = getattr(self, attr)
              setattr(self, attr, value)
  
      @property
      def source(self):
          """Source: Read-only. The current :class:`Source`, or ``None``."""
          return self._source  
  
      @property
      def time(self):
          """
          float: Read-only. Current playback time of the current source.
  
          The playback time is a float expressed in seconds, with 0.0 being the
          beginning of the media. The playback time returned represents the
          player master clock time which is used to synchronize both the audio
          and the video.
          """
          return self._mclock.get_time()
  
      def _create_texture(self):
          video_format = self.source.video_format
          self._texture = pyglet.image.Texture.create(
              video_format.width, video_format.height, rectangle=True)
          self._texture = self._texture.get_transform(flip_y=True)
          # After flipping the texture along the y axis, the anchor_y is set
          # to the top of the image. We want to keep it at the bottom.
          self._texture.anchor_y = 0
          return self._texture
  
      @property
      def texture(self):
          """
          :class:`pyglet.image.Texture`: Get the texture for the current video frame.
  
          You should call this method every time you display a frame of video,
          as multiple textures might be used. The return value will be None if
          there is no video in the current source.
          """
          return self._texture
  
      def get_texture(self):
          """
          Get the texture for the current video frame.
  
          You should call this method every time you display a frame of video,
          as multiple textures might be used. The return value will be None if
          there is no video in the current source.
  
          Returns:
              :class:`pyglet.image.Texture`
  
          .. deprecated:: 1.4
                  Use :attr:`~texture` instead
          """
          return self.texture
  
      def seek_next_frame(self):
          """Step forwards one video frame in the current source."""
          time = self.source.get_next_video_timestamp()
          if time is None:
              return
          self.seek(time)
  
      def update_texture(self, dt=None):
          """Manually update the texture from the current source.
  
          This happens automatically, so you shouldn't need to call this method.
  
          Args:
              dt (float): The time elapsed since the last call to
                  ``update_texture``.
          """
          # self.pr.disable()
          # if dt > 0.05:
          #     print("update_texture dt:", dt)
          #     import pstats
          #     ps = pstats.Stats(self.pr).sort_stats("cumulative")
          #     ps.print_stats()
          source = self.source
          time = self.time
          if bl.logger is not None:
              bl.logger.log(
                  "p.P.ut.1.0", dt, time,
                  self._audio_player.get_time() if self._audio_player else 0,
                  bl.logger.rebased_wall_time()
              )
  
          frame_rate = source.video_format.frame_rate
          frame_duration = 1 / frame_rate
          ts = source.get_next_video_timestamp()
          # Allow up to frame_duration difference
          while ts is not None and ts + frame_duration < time:
              source.get_next_video_frame()  # Discard frame
              if bl.logger is not None:
                  bl.logger.log("p.P.ut.1.5", ts)
              ts = source.get_next_video_timestamp()
  
          if bl.logger is not None:
              bl.logger.log("p.P.ut.1.6", ts)
  
          if ts is None:
              # No more video frames to show. End of video stream.
              if bl.logger is not None:
                  bl.logger.log("p.P.ut.1.7", frame_duration)
  
              pyglet.clock.schedule_once(self._video_finished, 0)
              return
  
          image = source.get_next_video_frame()
          if image is not None:
              if self._texture is None:
                  self._create_texture()
              self._texture.blit_into(image, 0, 0, 0)
          elif bl.logger is not None:
              bl.logger.log("p.P.ut.1.8")
  
          ts = source.get_next_video_timestamp()
          if ts is None:
              delay = frame_duration
          else:
              delay = ts - time
  
          delay = max(0.0, delay)
          if bl.logger is not None:
              bl.logger.log("p.P.ut.1.9", delay, ts)
          pyglet.clock.schedule_once(self.update_texture, delay)
          # self.pr.enable()
  
      def _video_finished(self, dt):
          if self._audio_player is None:
              self.dispatch_event("on_eos")
  
      volume = _PlayerProperty('volume', doc="""
      The volume level of sound playback.
  
      The nominal level is 1.0, and 0.0 is silence.
  
      The volume level is affected by the distance from the listener (if
      positioned).
      """)
      min_distance = _PlayerProperty('min_distance', doc="""
      The distance beyond which the sound volume drops by half, and within
      which no attenuation is applied.
  
      The minimum distance controls how quickly a sound is attenuated as it
      moves away from the listener. The gain is clamped at the nominal value
      within the min distance. By default the value is 1.0.
  
      The unit defaults to meters, but can be modified with the listener
      properties. """)
      max_distance = _PlayerProperty('max_distance', doc="""
      The distance at which no further attenuation is applied.
  
      When the distance from the listener to the player is greater than this
      value, attenuation is calculated as if the distance were value. By
      default the maximum distance is infinity.
  
      The unit defaults to meters, but can be modified with the listener
      properties.
      """)
      position = _PlayerProperty('position', doc="""
      The position of the sound in 3D space.
  
      The position is given as a tuple of floats (x, y, z). The unit
      defaults to meters, but can be modified with the listener properties.
      """)
      pitch = _PlayerProperty('pitch', doc="""
      The pitch shift to apply to the sound.
  
      The nominal pitch is 1.0. A pitch of 2.0 will sound one octave higher,
      and play twice as fast. A pitch of 0.5 will sound one octave lower, and
      play twice as slow. A pitch of 0.0 is not permitted.
      """)
      cone_orientation = _PlayerProperty('cone_orientation', doc="""
      The direction of the sound in 3D space.
  
      The direction is specified as a tuple of floats (x, y, z), and has no
      unit. The default direction is (0, 0, -1). Directional effects are only
      noticeable if the other cone properties are changed from their default
      values.
      """)
      cone_inner_angle = _PlayerProperty('cone_inner_angle', doc="""
      The interior angle of the inner cone.
  
      The angle is given in degrees, and defaults to 360. When the listener
      is positioned within the volume defined by the inner cone, the sound is
      played at normal gain (see :attr:`volume`).
      """)
      cone_outer_angle = _PlayerProperty('cone_outer_angle', doc="""
      The interior angle of the outer cone.
  
      The angle is given in degrees, and defaults to 360. When the listener
      is positioned within the volume defined by the outer cone, but outside
      the volume defined by the inner cone, the gain applied is a smooth
      interpolation between :attr:`volume` and :attr:`cone_outer_gain`.
      """)
      cone_outer_gain = _PlayerProperty('cone_outer_gain', doc="""
      The gain applied outside the cone.
  
      When the listener is positioned outside the volume defined by the outer
      cone, this gain is applied instead of :attr:`volume`.
      """)
  
      # Events
  
      def on_player_eos(self):
          """The player ran out of sources. The playlist is empty.
  
          :event:
          """
          if _debug:
              print('Player.on_player_eos')
  
      def on_eos(self):
          """The current source ran out of data.
  
          The default behaviour is to advance to the next source in the
          playlist if the :attr:`.loop` attribute is set to ``False``.
          If :attr:`.loop` attribute is set to ``True``, the current source
          will start to play again until :meth:`next_source` is called or
          :attr:`.loop` is set to ``False``.
  
          :event:
          """
          if _debug:
              print('Player.on_eos')
          if bl.logger is not None:
              bl.logger.log("p.P.oe")
              bl.logger.close()
  
          if self.loop:
              was_playing = self._playing
              self.pause()
              self._mclock.reset()
  
              if self.source:
                  # Reset source to the beginning
                  self.seek(0.0)
              self._audio_player.clear()
              self._set_playing(was_playing)
  
          else:
              self.next_source()
  
      def on_player_next_source(self):
          """The player starts to play the next queued source in the playlist.
  
          This is a useful event for adjusting the window size to the new
          source :class:`VideoFormat` for example.
  
          :event:
          """
          pass
  
  
  Player.register_event_type('on_eos')
  Player.register_event_type('on_player_eos')
  Player.register_event_type('on_player_next_source')
  
  
  def _one_item_playlist(source):
      yield source
  
  class PlayerGroup(object):
      """Group of players that can be played and paused simultaneously.
  
      Create a player group for the given list of players.
  
      All players in the group must currently not belong to any other group.
  
      Args:
          players (List[Player]): List of :class:`.Player` s in this group.
      """
  
      def __init__(self, players):
          """Initialize the PlayerGroup with the players."""
          self.players = list(players)
  
      def play(self):
          """Begin playing all players in the group simultaneously."""
          audio_players = [p._audio_player
                           for p in self.players if p._audio_player]
          if audio_players:
              audio_players[0]._play_group(audio_players)
          for player in self.players:
              player.play()
  
      def pause(self):
          """Pause all players in the group simultaneously."""
          audio_players = [p._audio_player
                           for p in self.players if p._audio_player]
          if audio_players:
              audio_players[0]._stop_group(audio_players)
          for player in self.players:
              player.pause()
  
files: pyglet/media/player.py
files: from __future__ import absolute_import, print_function
  import pyglet
  import pytest
  _debug = False
  
  
  class MockPlayer(object):
      def __init__(self, event_loop):
          self.event_loop = event_loop
          self.events = []
          self.pyclock = pyglet.clock.get_default()
          super(MockPlayer, self).__init__()
  
      def dispatch_event(self, event_type, *args):
          super(MockPlayer, self).dispatch_event(event_type, *args)
          if _debug:
              print('{}: event {} received @ {}'.format(
                  self.__class__.__name__,
                  event_type, self.pyclock.time()
              ))
          self.events.append((event_type, args))
          pyglet.clock.unschedule(self.event_loop.interrupt_event_loop)
          self.event_loop.interrupt_event_loop()
  
      def wait_for_event(self, timeout, *event_types):
          end_time = self.pyclock.time() + timeout
  
          while self.pyclock.time() < end_time:
              if _debug:
                  print('{}: run for {} sec @ {}'.format(self.__class__.__name__,
                        end_time - self.pyclock.time(), self.pyclock.time()))
              self.event_loop.run_event_loop(duration=end_time - self.pyclock.time())
              if not self.events:
                  continue
              event_type, args = self.events.pop()
              if event_type in event_types:
                  return event_type, args
          return None, None
  
      def wait_for_all_events(self, timeout, *expected_events):
          now = self.pyclock.time()
          if _debug:
              print('Wait for events @ {}'.format(now))
          end_time = self.pyclock.time() + timeout
          expected_events = list(expected_events)
          received_events = []
          while expected_events:
              event_type, args = self.wait_for_event(timeout, *expected_events)
              if _debug:
                  print('{}: got event {} @ {}'.format(self.__class__.__name__,
                        event_type, self.pyclock.time()))
              if event_type is None and self.pyclock.time() >= end_time:
                  pytest.fail('Timeout before all events have been received. '
                              'Still waiting for: ' + ','.join(expected_events))
              elif event_type is not None:
                  if event_type in expected_events:
                      expected_events.remove(event_type)
                  received_events.append((event_type, args))
          return received_events
  
      def wait(self, timeout):
          now = self.pyclock.time()
          end_time = now + timeout
          while now - end_time < -0.005:
              duration = max(.01, end_time - now)
              self.event_loop.run_event_loop(duration=duration)
              now = self.pyclock.time()
  
files: tests/integration/media/mock_player.py
files: from __future__ import absolute_import, print_function
  import pyglet
  _debug = False
  
  
  class MockPlayer(object):
      def __init__(self, event_loop):
          self.event_loop = event_loop
          self.events = []
          self.pyclock = pyglet.clock.get_default()
          super(MockPlayer, self).__init__()
  
      def dispatch_event(self, event_type, *args):
          super(MockPlayer, self).dispatch_event(event_type, *args)
          if _debug:
              print('{}: event {} received @ {}'.format(self.__class__.__name__,
              	event_type, self.pyclock.time()))
          self.events.append((event_type, args))
          pyglet.clock.unschedule(self.event_loop.interrupt_event_loop)
          self.event_loop.interrupt_event_loop()
  
      def wait_for_event(self, timeout, *event_types):
          end_time = self.pyclock.time() + timeout
  
          while self.pyclock.time() < end_time:
              if _debug:
                  print('{}: run for {} sec @ {}'.format(self.__class__.__name__,
                  	end_time-self.pyclock.time(), self.pyclock.time()))
              self.event_loop.run_event_loop(duration=end_time-self.pyclock.time())
              if not self.events:
                  continue
              event_type, args = self.events.pop()
              if event_type in event_types:
                  return event_type, args
          return None, None
  
      def wait_for_all_events(self, timeout, *expected_events):
          now = self.pyclock.time()
          if _debug:
              print('Wait for events @ {}'.format(now))
          end_time = self.pyclock.time() + timeout
          expected_events = list(expected_events)
          received_events = []
          while expected_events:
              event_type, args = self.wait_for_event(timeout, *expected_events)
              if _debug:
                  print('{}: got event {} @ {}'.format(self.__class__.__name__,
                  	event_type, self.pyclock.time()))
              if event_type is None and self.pyclock.time() >= end_time:
                  pytest.fail('Timeout before all events have been received. Still waiting for: '
                          + ','.join(expected_events))
              elif event_type is not None:
                  if event_type in expected_events:
                      expected_events.remove(event_type)
                  received_events.append((event_type, args))
          return received_events
  
      def wait(self, timeout):
          now = self.pyclock.time()
          end_time = now + timeout
          while now - end_time < -0.005:
              duration = max(.01, end_time-now)
              self.event_loop.run_event_loop(duration=duration)
              now = self.pyclock.time()
files: tests/integration/media/mock_player.py
files: from __future__ import print_function
  from future import standard_library
  standard_library.install_aliases()
  
  import pytest
  from tests import mock
  import time
  
  import pyglet
  _debug = False
  pyglet.options['debug_media'] = _debug
  pyglet.options['debug_media_buffers'] = _debug
  
  from pyglet.media import Player
  from pyglet.media.synthesis import Silence
  from .mock_player import MockPlayer
  
  
  class PlayerTest(MockPlayer, Player):
      pass
  
  
  @pytest.fixture
  def player(event_loop):
      return PlayerTest(event_loop)
  
  
  class SilentTestSource(Silence):
      def __init__(self, duration, sample_rate=44800, sample_size=16):
          super(Silence, self).__init__(duration, sample_rate, sample_size)
          self.bytes_read = 0
  
      def get_audio_data(self, nbytes, compensation_time=0.0):
          data = super(Silence, self).get_audio_data(nbytes, compensation_time)
          if data is not None:
              self.bytes_read += data.length
          return data
  
      def has_fully_played(self):
          return self.bytes_read == self._max_offset
  
  
  def test_player_play(player):
      source = SilentTestSource(.1)
      player.queue(source)
  
      player.play()
      player.wait_for_all_events(
          1.,
          'on_eos',
          'on_player_eos'
      )
      assert source.has_fully_played(), 'Source not fully played'
  
  
  def test_player_play_multiple(player):
      sources = (SilentTestSource(.1), SilentTestSource(.1))
      for source in sources:
          player.queue(source)
  
      player.play()
      player.wait_for_all_events(
          1.,
          'on_eos',
          'on_player_next_source',
          'on_eos',
          'on_player_eos'
      )
      for source in sources:
          assert source.has_fully_played(), 'Source not fully played'
  
  
  def test_multiple_fire_and_forget_players():
      """
      Test an issue where the driver crashed when starting multiple players, but not
      keeping a reference to these players.
      """
      for _ in range(10):
          Silence(1).play()
      time.sleep(1)
  
  
  def test_player_silent_audio_driver(player):
      with mock.patch('pyglet.media.player.get_audio_driver') as get_audio_driver_mock:
          get_audio_driver_mock.return_value = None
          source = SilentTestSource(.1)
          player.queue(source)
          player.play()
  
          player.wait_for_all_events(
              1.,
              'on_eos',
              'on_player_eos')
  
files: tests/integration/media/test_player.py
files: from __future__ import print_function
  from future import standard_library
  standard_library.install_aliases()
  
  import gc
  import pytest
  from tests import mock
  import time
  import unittest
  
  import pyglet
  _debug = False
  pyglet.options['debug_media'] = _debug
  pyglet.options['debug_media_buffers'] = _debug
  
  from pyglet.media import Player
  from pyglet.media.synthesis import Silence
  from .mock_player import MockPlayer
  
  
  class PlayerTest(MockPlayer, Player):
      pass
  
  
  @pytest.fixture
  def player(event_loop):
      return PlayerTest(event_loop)
  
  class SilentTestSource(Silence):
      def __init__(self, duration, sample_rate=44800, sample_size=16):
          super(Silence, self).__init__(duration, sample_rate, sample_size)
          self.bytes_read = 0
  
      def get_audio_data(self, nbytes, compensation_time=0.0):
          data = super(Silence, self).get_audio_data(nbytes, compensation_time)
          if data is not None:
              self.bytes_read += data.length
          return data
  
      def has_fully_played(self):
          return self.bytes_read == self._max_offset
  
  
  
  def test_player_play(player):
      source = SilentTestSource(.1)
      player.queue(source)
  
      player.play()
      player.wait_for_all_events(1., 
          'on_eos', 'on_player_eos')
      assert source.has_fully_played(), 'Source not fully played'
  
  
  def test_player_play_multiple(player):
      sources = (SilentTestSource(.1), SilentTestSource(.1))
      for source in sources:
          player.queue(source)
  
      player.play()
      player.wait_for_all_events(1., 
          'on_eos', 'on_player_next_source', 'on_eos', 'on_player_eos')
      for source in sources:
          assert source.has_fully_played(), 'Source not fully played'
  
  
  def test_multiple_fire_and_forget_players():
      """
      Test an issue where the driver crashed when starting multiple players, but not keeping a
      reference to these players.
      """
      for _ in range(10):
          Silence(1).play()
      time.sleep(1)
  
files: tests/integration/media/test_player.py
files: from __future__ import division
  from builtins import range
  from tests import mock
  import random
  from tests.base.future_test import FutureTestCase
  
  from pyglet.media.player import Player, PlayerGroup
  from pyglet.media.codecs.base import AudioFormat, VideoFormat, Source
  
  # pyglet.options['debug_media'] = True
  
  
  class PlayerTestCase(FutureTestCase):
      # Default values to use
      audio_format_1 = AudioFormat(1, 8, 11025)
      audio_format_2 = AudioFormat(2, 8, 11025)
      audio_format_3 = AudioFormat(2, 16, 44100)
      video_format_1 = VideoFormat(800, 600)
      video_format_2 = VideoFormat(1920, 1280)
      video_format_2.frame_rate = 25
  
      def setUp(self):
          self.player = Player()
  
          self._get_audio_driver_patcher = mock.patch('pyglet.media.player.get_audio_driver')
          self.mock_get_audio_driver = self._get_audio_driver_patcher.start()
          self.mock_audio_driver = self.mock_get_audio_driver.return_value
          self.mock_audio_driver_player = self.mock_audio_driver.create_audio_player.return_value
  
          self._clock_patcher = mock.patch('pyglet.clock')
          self.mock_clock = self._clock_patcher.start()
  
          self._texture_patcher = mock.patch('pyglet.image.Texture.create')
          self.mock_texture_create = self._texture_patcher.start()
          self.mock_texture = self.mock_texture_create.return_value
          # Need to do this as side_effect instead of return_value, or reset_mock will recurse
          self.mock_texture.get_transform.side_effect = lambda flip_y: self.mock_texture
  
      def tearDown(self):
          self._get_audio_driver_patcher.stop()
          self._clock_patcher.stop()
          self._texture_patcher.stop()
  
      def reset_mocks(self):
          # These mocks will recursively reset their children
          self.mock_get_audio_driver.reset_mock()
          self.mock_clock.reset_mock()
          self.mock_texture_create.reset_mock()
  
      def create_mock_source(self, audio_format, video_format):
          mock_source = mock.MagicMock(spec=Source)
          type(mock_source).audio_format = mock.PropertyMock(return_value=audio_format)
          type(mock_source).video_format = mock.PropertyMock(return_value=video_format)
          type(mock_source._get_queue_source.return_value).audio_format = mock.PropertyMock(return_value=audio_format)
          type(mock_source._get_queue_source.return_value).video_format = mock.PropertyMock(return_value=video_format)
          if video_format:
              mock_source.video_format.frame_rate = 30
          return mock_source
  
      def set_video_data_for_mock_source(self, mock_source, timestamp_data_pairs):
          """Make the given mock source return video data. Video data is given in pairs of
          timestamp and data to return."""
          def _get_frame():
              if timestamp_data_pairs:
                  current_frame = timestamp_data_pairs.pop(0)
                  return current_frame[1]
  
          def _get_timestamp():
              if timestamp_data_pairs:
                  return timestamp_data_pairs[0][0]
          queue_source = mock_source._get_queue_source.return_value
          queue_source.get_next_video_timestamp.side_effect = _get_timestamp
          queue_source.get_next_video_frame.side_effect = _get_frame
  
      def assert_not_playing_yet(self, current_source=None):
          """Assert the the player did not start playing yet."""
          self.assertFalse(self.mock_get_audio_driver.called, msg='No audio driver required yet')
          self.assertAlmostEqual(self.player.time, 0.)
          self.assert_not_playing(current_source)
  
      def assert_not_playing(self, current_source=None):
          self._assert_playing(False, current_source)
  
      def assert_now_playing(self, current_source):
          self._assert_playing(True, current_source)
  
      def _assert_playing(self, playing, current_source=None):
          self.assertEqual(self.player.playing, playing)
          queued_source = (current_source._get_queue_source.return_value
                           if current_source is not None
                           else None)
          self.assertIs(self.player.source, queued_source)
  
      def assert_driver_player_created_for(self, source):
          """Assert that a driver specific audio player is created to play back given source"""
          self._assert_player_created_for(self.mock_get_audio_driver, self.mock_audio_driver, source)
  
      def _assert_player_created_for(self, mock_get_audio_driver, mock_audio_driver, source):
          mock_get_audio_driver.assert_called_once_with()
          self.assertEqual(mock_audio_driver.create_audio_player.call_count, 1)
          call_args = mock_audio_driver.create_audio_player.call_args
          args, kwargs = call_args
          arg_source, arg_player = args
          self.assertIs(arg_source, source._get_queue_source.return_value)
          self.assertIs(arg_player, self.player)
  
      def assert_no_new_driver_player_created(self):
          """Assert that no new driver specific audio player is created."""
          self.assertFalse(self.mock_get_audio_driver.called, msg='No new audio driver should be created')
  
      # def assert_in_current_play_list(self, *sources):
      #     self.assertIsNotNone(self.current_play_list, msg='No previous call to create driver player')
  
      #     queue_sources = deque(source._get_queue_source.return_value for source in sources)
      #     self.assertSequenceEqual(self.current_play_list._sources, queue_sources)
  
      def assert_driver_player_destroyed(self):
          self.mock_audio_driver_player.delete.assert_called_once_with()
  
      def assert_driver_player_not_destroyed(self):
          self.assertFalse(self.mock_audio_driver_player.delete.called)
  
      def assert_silent_driver_player_destroyed(self):
          self.mock_silent_audio_driver_player.delete.assert_called_once_with()
  
      def assert_driver_player_started(self):
          self.mock_audio_driver_player.play.assert_called_once_with()
  
      def assert_driver_player_stopped(self):
          self.mock_audio_driver_player.stop.assert_called_once_with()
  
      def assert_driver_player_cleared(self):
          self.mock_audio_driver_player.clear.assert_called_once_with()
  
      def assert_source_seek(self, source, time):
          source._get_queue_source.return_value.seek.assert_called_once_with(time)
  
      def assert_new_texture_created(self, video_format):
          self.mock_texture_create.assert_called_once_with(video_format.width, video_format.height, rectangle=True)
  
      def assert_no_new_texture_created(self):
          self.assertFalse(self.mock_texture_create.called)
  
      def assert_texture_updated(self, frame_data):
          self.mock_texture.blit_into.assert_called_once_with(frame_data, 0, 0, 0)
  
      def assert_texture_not_updated(self):
          self.assertFalse(self.mock_texture.blit_into.called)
  
      def assert_update_texture_scheduled(self):
          self.mock_clock.schedule_once.assert_called_once_with(self.player.update_texture, 0)
  
      def assert_update_texture_unscheduled(self):
          self.mock_clock.unschedule.assert_called_with(self.player.update_texture)
  
      def pretend_player_at_time(self, t):
          self.player._mclock.set_time(t)
  
      def pretend_silent_driver_player_at_time(self, t):
          self.mock_silent_audio_driver_player.get_time.return_value = t
  
      def test_queue_single_audio_source_and_play(self):
          """Queue a single audio source and start playing it."""
  
          mock_source = self.create_mock_source(self.audio_format_1, None)
          self.player.queue(mock_source)
          self.assert_not_playing_yet(mock_source)
  
          self.player.play()
          self.assert_driver_player_created_for(mock_source)
          self.assert_driver_player_started()
          self.assert_now_playing(mock_source)
  
      def test_queue_multiple_audio_sources_same_format_and_play(self):
          """Queue multiple audio sources using the same audio format and start playing."""
          mock_source1 = self.create_mock_source(self.audio_format_1, None)
          mock_source2 = self.create_mock_source(self.audio_format_1, None)
          mock_source3 = self.create_mock_source(self.audio_format_1, None)
  
          self.player.queue(mock_source1)
          self.assert_not_playing_yet(mock_source1)
  
          self.player.queue(mock_source2)
          self.assert_not_playing_yet(mock_source1)
  
          self.player.queue(mock_source3)
          self.assert_not_playing_yet(mock_source1)
  
          self.player.play()
          self.assert_driver_player_created_for(mock_source1)
          self.assert_driver_player_started()
          self.assert_now_playing(mock_source1)
  
      def test_queue_multiple_audio_sources_different_format_and_play_and_skip(self):
          """Queue multiple audio sources having different formats and start playing. Different
          formats should be played by seperate driver players."""
          mock_source1 = self.create_mock_source(self.audio_format_1, None)
          mock_source2 = self.create_mock_source(self.audio_format_2, None)
          mock_source3 = self.create_mock_source(self.audio_format_3, None)
  
          self.player.queue(mock_source1)
          self.assert_not_playing_yet(mock_source1)
  
          self.player.queue(mock_source2)
          self.assert_not_playing_yet(mock_source1)
  
          self.player.queue(mock_source3)
          self.assert_not_playing_yet(mock_source1)
  
          self.player.play()
          self.assert_driver_player_created_for(mock_source1)
          self.assert_driver_player_started()
          self.assert_now_playing(mock_source1)
  
          self.reset_mocks()
          self.player.next_source()
          self.assert_driver_player_destroyed()
          self.assert_driver_player_created_for(mock_source2)
          self.assert_driver_player_started()
          self.assert_now_playing(mock_source2)
  
          self.reset_mocks()
          self.player.next_source()
          self.assert_driver_player_destroyed()
          self.assert_driver_player_created_for(mock_source3)
          self.assert_driver_player_started()
          self.assert_now_playing(mock_source3)
  
      def test_queue_multiple_audio_sources_same_format_and_play_and_skip(self):
          """When multiple audio sources with the same format are queued, they are played
          using the same driver player. Skipping to the next source is just advancing the
          source group.
          """
          mock_source1 = self.create_mock_source(self.audio_format_1, None)
          mock_source2 = self.create_mock_source(self.audio_format_1, None)
          mock_source3 = self.create_mock_source(self.audio_format_1, None)
  
          self.player.queue(mock_source1)
          self.player.queue(mock_source2)
          self.player.queue(mock_source3)
  
          self.player.play()
          self.assert_driver_player_created_for(mock_source1)
          self.assert_driver_player_started()
          self.assert_now_playing(mock_source1)
  
          self.reset_mocks()
          self.player.next_source()
          self.assert_driver_player_not_destroyed()
          self.assert_no_new_driver_player_created()
          self.assert_now_playing(mock_source2)
  
          self.reset_mocks()
          self.player.next_source()
          self.assert_driver_player_not_destroyed()
          self.assert_no_new_driver_player_created()
          self.assert_now_playing(mock_source3)
  
      def test_on_eos(self):
          """The player receives on_eos for every source, but does not need to do anything.
          """
          mock_source1 = self.create_mock_source(self.audio_format_1, None)
          mock_source2 = self.create_mock_source(self.audio_format_1, None)
          mock_source3 = self.create_mock_source(self.audio_format_1, None)
  
          self.player.queue(mock_source1)
          self.player.queue(mock_source2)
          self.player.queue(mock_source3)
  
          self.player.play()
          self.assert_driver_player_created_for(mock_source1)
          self.assert_driver_player_started()
  
          self.reset_mocks()
          self.player.dispatch_event('on_eos')
          self.assert_driver_player_not_destroyed()
  
      def test_player_stops_after_last_eos(self):
          """If the last playlist source is eos, the player stops."""
          mock_source = self.create_mock_source(self.audio_format_1, None)
          self.player.queue(mock_source)
          self.assert_not_playing_yet(mock_source)
  
          self.player.play()
          self.assert_driver_player_created_for(mock_source)
          self.assert_driver_player_started()
          self.assert_now_playing(mock_source)
  
          self.reset_mocks()
          self.player.dispatch_event('on_eos')
          self.assert_driver_player_destroyed()
          self.assert_not_playing(None)
  
      def test_eos_events(self):
          """Test receiving various eos events: on source eos,
          on playlist exhausted and on player eos and on player next source.
          """
          on_eos_mock = mock.MagicMock(return_value=None)
          self.player.event('on_eos')(on_eos_mock)
          on_player_eos_mock = mock.MagicMock(return_value=None)
          self.player.event('on_player_eos')(on_player_eos_mock)
          on_player_next_source_mock = mock.MagicMock(return_value=None)
          self.player.event('on_player_next_source')(on_player_next_source_mock)
  
          def reset_eos_mocks():
              on_eos_mock.reset_mock()
              on_player_eos_mock.reset_mock()
              on_player_next_source_mock.reset_mock()
  
          def assert_eos_events_received(on_eos=False, on_player_eos=False,
                                         on_player_next_source=False):
              self.assertEqual(on_eos_mock.called, on_eos)
              self.assertEqual(on_player_eos_mock.called, on_player_eos)
              self.assertEqual(on_player_next_source_mock.called, on_player_next_source)
  
          mock_source1 = self.create_mock_source(self.audio_format_1, None)
          mock_source2 = self.create_mock_source(self.audio_format_1, None)
          mock_source3 = self.create_mock_source(self.audio_format_2, None)
  
          self.player.queue(mock_source1)
          self.player.queue(mock_source2)
          self.player.queue(mock_source3)
          self.assert_not_playing_yet(mock_source1)
  
          self.player.play()
          self.assert_driver_player_created_for(mock_source1)
  
          self.reset_mocks()
          reset_eos_mocks()
          # Pretend the current source in the group was eos and next source started
          self.player.dispatch_event('on_eos')
          self.assert_driver_player_not_destroyed()
          assert_eos_events_received(on_eos=True, on_player_next_source=True)
          self.assertEqual(len(self.player._playlists), 2)
  
          # Pretend playlist is exhausted. Should be no more sources to play.
          self.player.dispatch_event('on_eos')
          self.reset_mocks()
          reset_eos_mocks()
          self.player.dispatch_event('on_eos')
          self.assert_not_playing(None)
          assert_eos_events_received(on_eos=True, on_player_eos=True)
  
      def test_pause_resume(self):
          """A stream can be paused. After that play will resume where paused."""
          mock_source = self.create_mock_source(self.audio_format_1, None)
          self.player.queue(mock_source)
          self.player.play()
          self.assert_driver_player_created_for(mock_source)
          self.assert_driver_player_started()
          self.assert_now_playing(mock_source)
  
          self.reset_mocks()
          self.pretend_player_at_time(0.5)
          self.player.pause()
          self.assert_driver_player_stopped()
          self.assert_driver_player_not_destroyed()
  
          self.reset_mocks()
          self.player.play()
          self.assertAlmostEqual(
              self.player.time, 0.5, places=2,
              msg='While playing, player should return time from driver player'
          )
          self.assert_driver_player_started()
          self.assert_no_new_driver_player_created()
          self.assert_now_playing(mock_source)
  
      def test_delete(self):
          """Test clean up of the player when delete() is called."""
          mock_source1 = self.create_mock_source(self.audio_format_1, None)
          mock_source2 = self.create_mock_source(self.audio_format_2, None)
          mock_source3 = self.create_mock_source(self.audio_format_3, None)
  
          self.player.queue(mock_source1)
          self.player.queue(mock_source2)
          self.player.queue(mock_source3)
          self.assert_not_playing_yet(mock_source1)
  
          self.player.play()
          self.assert_driver_player_created_for(mock_source1)
          self.assert_driver_player_started()
  
          self.reset_mocks()
          self.pretend_player_at_time(1.)
          self.player.delete()
          self.assert_driver_player_destroyed()
  
      def test_empty_player(self):
          """A player without queued sources should not start a driver player and should not raise
          exceptions"""
          self.assert_not_playing_yet(None)
  
          self.reset_mocks()
          self.player.play()
          self.assert_no_new_driver_player_created()
  
          self.reset_mocks()
          self.player.pause()
          self.assert_no_new_driver_player_created()
          self.assert_driver_player_not_destroyed()
  
          self.reset_mocks()
          self.player.next_source()
          self.assert_no_new_driver_player_created()
          self.assert_driver_player_not_destroyed()
  
          self.reset_mocks()
          self.player.seek(0.8)
          self.assert_no_new_driver_player_created()
          self.assert_driver_player_not_destroyed()
  
          self.player.delete()
  
      def test_set_player_properties_before_playing(self):
          """When setting player properties before a driver specific player is
          created, these settings should be propagated after creating the
          player.
          """
          mock_source1 = self.create_mock_source(self.audio_format_1, None)
          mock_source2 = self.create_mock_source(self.audio_format_2, None)
          self.player.queue(mock_source1)
          self.player.queue(mock_source2)
          self.assert_not_playing_yet(mock_source1)
  
          self.reset_mocks()
          self.player.volume = 10.
          self.player.min_distance = 2.
          self.player.max_distance = 3.
          self.player.position = (4, 4, 4)
          self.player.pitch = 5.0
          self.player.cone_orientation = (6, 6, 6)
          self.player.cone_inner_angle = 7.
          self.player.cone_outer_angle = 8.
          self.player.cone_outer_gain = 9.
  
          def assert_properties_set():
              self.mock_audio_driver_player.set_volume.assert_called_once_with(10.)
              self.mock_audio_driver_player.set_min_distance.assert_called_once_with(2.)
              self.mock_audio_driver_player.set_max_distance.assert_called_once_with(3.)
              self.mock_audio_driver_player.set_position.assert_called_once_with((4, 4, 4))
              self.mock_audio_driver_player.set_pitch.assert_called_once_with(5.)
              self.mock_audio_driver_player.set_cone_orientation.assert_called_once_with((6, 6, 6))
              self.mock_audio_driver_player.set_cone_inner_angle.assert_called_once_with(7.)
              self.mock_audio_driver_player.set_cone_outer_angle.assert_called_once_with(8.)
              self.mock_audio_driver_player.set_cone_outer_gain.assert_called_once_with(9.)
  
          self.reset_mocks()
          self.player.play()
          self.assert_driver_player_created_for(mock_source1)
          self.assert_now_playing(mock_source1)
          assert_properties_set()
  
          self.reset_mocks()
          self.player.next_source()
          self.assert_driver_player_destroyed()
          self.assert_driver_player_created_for(mock_source2)
          assert_properties_set()
  
      def test_set_player_properties_while_playing(self):
          """When setting player properties while playing, the properties should
          be propagated to the driver specific player right away."""
          mock_source1 = self.create_mock_source(self.audio_format_1, None)
          mock_source2 = self.create_mock_source(self.audio_format_2, None)
          self.player.queue(mock_source1)
          self.player.queue(mock_source2)
          self.assert_not_playing_yet(mock_source1)
  
          self.reset_mocks()
          self.player.play()
          self.assert_driver_player_created_for(mock_source1)
          self.assert_now_playing(mock_source1)
  
          self.reset_mocks()
          self.player.volume = 10.
          self.mock_audio_driver_player.set_volume.assert_called_once_with(10.)
  
          self.reset_mocks()
          self.player.min_distance = 2.
          self.mock_audio_driver_player.set_min_distance.assert_called_once_with(2.)
  
          self.reset_mocks()
          self.player.max_distance = 3.
          self.mock_audio_driver_player.set_max_distance.assert_called_once_with(3.)
  
          self.reset_mocks()
          self.player.position = (4, 4, 4)
          self.mock_audio_driver_player.set_position.assert_called_once_with((4, 4, 4))
  
          self.reset_mocks()
          self.player.pitch = 5.0
          self.mock_audio_driver_player.set_pitch.assert_called_once_with(5.)
  
          self.reset_mocks()
          self.player.cone_orientation = (6, 6, 6)
          self.mock_audio_driver_player.set_cone_orientation.assert_called_once_with((6, 6, 6))
  
          self.reset_mocks()
          self.player.cone_inner_angle = 7.
          self.mock_audio_driver_player.set_cone_inner_angle.assert_called_once_with(7.)
  
          self.reset_mocks()
          self.player.cone_outer_angle = 8.
          self.mock_audio_driver_player.set_cone_outer_angle.assert_called_once_with(8.)
  
          self.reset_mocks()
          self.player.cone_outer_gain = 9.
          self.mock_audio_driver_player.set_cone_outer_gain.assert_called_once_with(9.)
  
          self.reset_mocks()
          self.player.next_source()
          self.assert_driver_player_destroyed()
          self.assert_driver_player_created_for(mock_source2)
          self.mock_audio_driver_player.set_volume.assert_called_once_with(10.)
          self.mock_audio_driver_player.set_min_distance.assert_called_once_with(2.)
          self.mock_audio_driver_player.set_max_distance.assert_called_once_with(3.)
          self.mock_audio_driver_player.set_position.assert_called_once_with((4, 4, 4))
          self.mock_audio_driver_player.set_pitch.assert_called_once_with(5.)
          self.mock_audio_driver_player.set_cone_orientation.assert_called_once_with((6, 6, 6))
          self.mock_audio_driver_player.set_cone_inner_angle.assert_called_once_with(7.)
          self.mock_audio_driver_player.set_cone_outer_angle.assert_called_once_with(8.)
          self.mock_audio_driver_player.set_cone_outer_gain.assert_called_once_with(9.)
  
      def test_seek(self):
          """Test seeking to a specific time in the current source."""
          mock_source = self.create_mock_source(self.audio_format_1, None)
          self.player.queue(mock_source)
          self.assert_not_playing_yet(mock_source)
  
          self.reset_mocks()
          mock_source.reset_mock()
          self.player.seek(0.7)
          self.assert_source_seek(mock_source, 0.7)
  
          self.reset_mocks()
          mock_source.reset_mock()
          self.player.play()
          self.assert_driver_player_created_for(mock_source)
          self.assert_now_playing(mock_source)
  
          self.reset_mocks()
          mock_source.reset_mock()
          self.player.seek(0.2)
          self.assert_source_seek(mock_source, 0.2)
          # Clear buffers for immediate result
          self.assert_driver_player_cleared()
  
      def test_video_queue_and_play(self):
          """Sources can also include video. Instead of using a player to
          continuously play the video, a texture is updated based on the
          video packet timestamp."""
          mock_source = self.create_mock_source(self.audio_format_1, self.video_format_1)
          self.set_video_data_for_mock_source(mock_source, [(0.2, 'a')])
          self.player.queue(mock_source)
          self.assert_not_playing_yet(mock_source)
  
          self.reset_mocks()
          self.player.play()
          self.assert_driver_player_created_for(mock_source)
          self.assert_driver_player_started()
          self.assert_now_playing(mock_source)
          self.assert_new_texture_created(self.video_format_1)
          self.assert_update_texture_scheduled()
  
          self.reset_mocks()
          self.pretend_player_at_time(0.2)
          self.player.update_texture()
          self.assert_texture_updated('a')
          self.assertIs(self.player.texture, self.mock_texture)
  
      def test_video_seek(self):
          """Sources with video can also be seeked. It's the Source
          responsibility to present the Player with audio and video at the
          correct time."""
          mock_source = self.create_mock_source(self.audio_format_1, self.video_format_1)
          self.set_video_data_for_mock_source(mock_source, [(0.0, 'a'), (0.1, 'b'), (0.2, 'c'),
                                                            (0.3, 'd'), (0.4, 'e'), (0.5, 'f')])
          self.player.queue(mock_source)
          self.player.play()
          self.assert_new_texture_created(self.video_format_1)
          self.assert_update_texture_scheduled()
  
          self.reset_mocks()
          self.pretend_player_at_time(0.0)
          self.player.update_texture()
          self.assert_texture_updated('a')
  
          self.reset_mocks()
          self.player.seek(0.3)
          self.assert_source_seek(mock_source, 0.3)
          self.assert_no_new_texture_created()
          self.assert_texture_updated('d')
  
          self.reset_mocks()
          self.pretend_player_at_time(0.4)
          self.player.update_texture()
          self.assert_texture_updated('e')
  
      def test_video_frame_rate(self):
          """Videos texture are scheduled according to the video packet
          timestamp."""
          mock_source1 = self.create_mock_source(self.audio_format_1, self.video_format_1)
          mock_source2 = self.create_mock_source(self.audio_format_1, self.video_format_2)
          for mock_source in (mock_source1, mock_source2):
              self.set_video_data_for_mock_source(
                  mock_source,
                  [(0.0, 'a'), (0.1, 'b'), (0.2, 'c'),
                   (0.3, 'd'), (0.4, 'e'), (0.5, 'f')]
              )
  
          self.player.queue(mock_source1)
          self.player.queue(mock_source2)
  
          self.player.play()
          self.assert_new_texture_created(self.video_format_1)
          self.assert_update_texture_scheduled()
  
          self.reset_mocks()
          self.player.next_source()
          self.assert_new_texture_created(self.video_format_2)
          self.assert_update_texture_unscheduled()
          # schedule_once called twice:
          #   - Once for seeking back to 0 the previous source in next_source()
          #   - Once for scheduling the next source update_texture
          assert self.mock_clock.schedule_once.call_count == 2
          self.mock_clock.schedule_once.assert_called_with(self.player.update_texture, 0)
  
      def test_video_seek_next_frame(self):
          """It is possible to jump directly to the next frame of video and adjust the audio player
          accordingly."""
          mock_source = self.create_mock_source(self.audio_format_1, self.video_format_1)
          self.set_video_data_for_mock_source(mock_source, [(0.0, 'a'), (0.2, 'b')])
          self.player.queue(mock_source)
          self.player.play()
          self.assert_new_texture_created(self.video_format_1)
          self.assert_update_texture_scheduled()
  
          self.reset_mocks()
          self.pretend_player_at_time(0.0)
          self.player.update_texture()
          self.assert_texture_updated('a')
  
          self.reset_mocks()
          self.player.seek_next_frame()
          self.assert_source_seek(mock_source, 0.2)
          self.assert_texture_updated('b')
  
      def test_video_runs_out_of_frames(self):
          """When the video runs out of frames, it stops updating the texture. The audio player is
          responsible for triggering eos."""
          mock_source = self.create_mock_source(self.audio_format_1, self.video_format_1)
          self.set_video_data_for_mock_source(mock_source, [(0.0, 'a'), (0.1, 'b')])
          self.player.queue(mock_source)
          self.player.play()
          self.assert_new_texture_created(self.video_format_1)
          self.assert_update_texture_scheduled()
  
          self.reset_mocks()
          self.pretend_player_at_time(0.0)
          self.player.update_texture()
          self.assert_texture_updated('a')
  
          self.reset_mocks()
          self.pretend_player_at_time(0.1)
          self.player.update_texture()
          self.assert_texture_updated('b')
  
          self.reset_mocks()
          self.pretend_player_at_time(0.2)
          self.player.update_texture()
          self.assert_texture_not_updated()
  
          self.reset_mocks()
          self.player.seek_next_frame()
          self.assert_texture_not_updated()
  
      def test_video_without_audio(self):
          """It is possible to have videos without audio streams."""
          mock_source = self.create_mock_source(None, self.video_format_1)
          self.player.queue(mock_source)
          self.player.play()
          self.assert_new_texture_created(self.video_format_1)
          self.assert_update_texture_scheduled()
          self.assert_no_new_driver_player_created()
  
      def test_audio_source_with_silent_driver(self):
          """An audio source with a silent driver."""
          mock_source = self.create_mock_source(self.audio_format_3, None)
          self.mock_get_audio_driver.return_value = None
          self.player.queue(mock_source)
          self.player.play()
  
  
  class PlayerGroupTestCase(FutureTestCase):
      def create_mock_player(self, has_audio=True):
          player = mock.MagicMock()
          if has_audio:
              audio_player = mock.PropertyMock(return_value=mock.MagicMock())
          else:
              audio_player = mock.PropertyMock(return_value=None)
          type(player)._audio_player = audio_player
          return player
  
      def assert_players_started(self, *players):
          for player in players:
              player.play.assert_called_once_with()
  
      def assert_audio_players_started(self, *players):
          # Find the one player that was used to start the group,
          # the rest should not be used
          call_args = None
          audio_players = []
          for player in players:
              audio_player = player._audio_player
              audio_players.append(audio_player)
              if call_args is not None:
                  self.assertFalse(audio_player._play_group.called,
                                   msg='Only one player should be used to start the group')
              elif audio_player._play_group.called:
                  call_args = audio_player._play_group.call_args
  
          self.assertIsNotNone(call_args,
                               msg='No player was used to start all audio players.')
          started_players = call_args[0][0]
          self.assertCountEqual(started_players, audio_players,
                                msg='Not all players with audio players were started')
  
      def assert_players_stopped(self, *players):
          for player in players:
              player.pause.assert_called_once_with()
  
      def assert_audio_players_stopped(self, *players):
          # Find the one player that was used to start the group,
          # the rest should not be used
          call_args = None
          audio_players = []
          for player in players:
              audio_player = player._audio_player
              audio_players.append(audio_player)
              if call_args is not None:
                  self.assertFalse(audio_player._stop_group.called,
                                   msg='Only one player should be used to stop the group')
              elif audio_player._stop_group.called:
                  call_args = audio_player._stop_group.call_args
  
          self.assertIsNotNone(call_args,
                               msg='No player was used to stop all audio players.')
          stopped_players = call_args[0][0]
          self.assertCountEqual(stopped_players, audio_players,
                                msg='Not all players with audio players were stopped')
  
      def reset_mocks(self, *mocks):
          for m in mocks:
              m.reset_mock()
  
      def test_empty_group(self):
          """Just check nothing explodes on an empty group."""
          group = PlayerGroup([])
          group.play()
          group.pause()
  
      def test_only_with_audio(self):
          """Test a group containing only players with audio."""
          players = [self.create_mock_player(has_audio=True) for _ in range(10)]
          group = PlayerGroup(players)
  
          group.play()
          self.assert_audio_players_started(*players)
          self.assert_players_started(*players)
          self.reset_mocks(*players)
  
          group.pause()
          self.assert_audio_players_stopped(*players)
          self.assert_players_stopped(*players)
  
      def test_only_without_audio(self):
          """Test a group containing only players without audio."""
          players = [self.create_mock_player(has_audio=False) for _ in range(10)]
          group = PlayerGroup(players)
  
          group.play()
          self.assert_players_started(*players)
          self.reset_mocks(*players)
  
          group.pause()
          self.assert_players_stopped(*players)
  
      def test_mixed_players(self):
          """Test a group containing both players with audio and players without audio."""
          players_with_audio = [self.create_mock_player(has_audio=True) for _ in range(10)]
          players_without_audio = [self.create_mock_player(has_audio=False) for _ in range(10)]
          players = players_with_audio + players_without_audio
          random.shuffle(players)
          group = PlayerGroup(players)
  
          group.play()
          self.assert_audio_players_started(*players_with_audio)
          self.assert_players_started(*players)
          self.reset_mocks(*players)
  
          group.pause()
          self.assert_audio_players_stopped(*players_with_audio)
          self.assert_players_stopped(*players)
  
files: tests/unit/media/test_player.py
files: from __future__ import division
  from builtins import range
  import ctypes
  from tests import mock
  import os
  import random
  from collections import deque
  from itertools import product
  from tests.base.future_test import FutureTestCase
  
  import pytest
  
  import pyglet
  from pyglet.media.player import Player, PlayerGroup
  from pyglet.media.codecs.base import *
  
  #pyglet.options['debug_media'] = True
  
  
  class PlayerTestCase(FutureTestCase):
      # Default values to use
      audio_format_1 = AudioFormat(1, 8, 11025)
      audio_format_2 = AudioFormat(2, 8, 11025)
      audio_format_3 = AudioFormat(2, 16, 44100)
      video_format_1 = VideoFormat(800, 600)
      video_format_2 = VideoFormat(1920, 1280)
      video_format_2.frame_rate = 25
  
      def setUp(self):
          self.player = Player()
  
          self._get_audio_driver_patcher = mock.patch('pyglet.media.player.get_audio_driver')
          self.mock_get_audio_driver = self._get_audio_driver_patcher.start()
          self.mock_audio_driver = self.mock_get_audio_driver.return_value
          self.mock_audio_driver_player = self.mock_audio_driver.create_audio_player.return_value
  
          self._clock_patcher = mock.patch('pyglet.clock')
          self.mock_clock = self._clock_patcher.start()
  
          self._texture_patcher = mock.patch('pyglet.image.Texture.create')
          self.mock_texture_create = self._texture_patcher.start()
          self.mock_texture = self.mock_texture_create.return_value
          # Need to do this as side_effect instead of return_value, or reset_mock will recurse
          self.mock_texture.get_transform.side_effect = lambda flip_y: self.mock_texture
  
      def tearDown(self):
          self._get_audio_driver_patcher.stop()
          self._clock_patcher.stop()
          self._texture_patcher.stop()
  
      def reset_mocks(self):
          # These mocks will recursively reset their children
          self.mock_get_audio_driver.reset_mock()
          self.mock_clock.reset_mock()
          self.mock_texture_create.reset_mock()
  
      def create_mock_source(self, audio_format, video_format):
          mock_source = mock.MagicMock(spec=Source)
          type(mock_source).audio_format = mock.PropertyMock(return_value=audio_format)
          type(mock_source).video_format = mock.PropertyMock(return_value=video_format)
          type(mock_source._get_queue_source.return_value).audio_format = mock.PropertyMock(return_value=audio_format)
          type(mock_source._get_queue_source.return_value).video_format = mock.PropertyMock(return_value=video_format)
          if video_format:
              mock_source.video_format.frame_rate = 30
          return mock_source
  
      def set_video_data_for_mock_source(self, mock_source, timestamp_data_pairs):
          """Make the given mock source return video data. Video data is given in pairs of timestamp
          and data to return."""
          def _get_frame():
              if timestamp_data_pairs:
                  current_frame = timestamp_data_pairs.pop(0)
                  return current_frame[1]
          def _get_timestamp():
              if timestamp_data_pairs:
                  return timestamp_data_pairs[0][0]
          queue_source = mock_source._get_queue_source.return_value
          queue_source.get_next_video_timestamp.side_effect = _get_timestamp
          queue_source.get_next_video_frame.side_effect = _get_frame
  
      def assert_not_playing_yet(self, current_source=None):
          """Assert the the player did not start playing yet."""
          self.assertFalse(self.mock_get_audio_driver.called, msg='No audio driver required yet')
          self.assertAlmostEqual(self.player.time, 0.)
          self.assert_not_playing(current_source)
  
      def assert_not_playing(self, current_source=None):
          self._assert_playing(False, current_source)
  
      def assert_now_playing(self, current_source):
          self._assert_playing(True, current_source)
  
      def _assert_playing(self, playing, current_source=None):
          self.assertEqual(self.player.playing, playing)
          queued_source = (current_source._get_queue_source.return_value
                           if current_source is not None
                           else None)
          self.assertIs(self.player.source, queued_source)
  
      def assert_driver_player_created_for(self, source):
          """Assert that a driver specific audio player is created to play back given source"""
          self._assert_player_created_for(self.mock_get_audio_driver, self.mock_audio_driver, source)
  
      def _assert_player_created_for(self, mock_get_audio_driver, mock_audio_driver, source):
          mock_get_audio_driver.assert_called_once_with()
          self.assertEqual(mock_audio_driver.create_audio_player.call_count, 1)
          call_args = mock_audio_driver.create_audio_player.call_args
          args, kwargs = call_args
          arg_source, arg_player = args
          self.assertIs(arg_source, source._get_queue_source.return_value)
          self.assertIs(arg_player, self.player)
  
      def assert_no_new_driver_player_created(self):
          """Assert that no new driver specific audio player is created."""
          self.assertFalse(self.mock_get_audio_driver.called, msg='No new audio driver should be created')
  
      # def assert_in_current_play_list(self, *sources):
      #     self.assertIsNotNone(self.current_play_list, msg='No previous call to create driver player')
  
      #     queue_sources = deque(source._get_queue_source.return_value for source in sources)
      #     self.assertSequenceEqual(self.current_play_list._sources, queue_sources)
  
      def assert_driver_player_destroyed(self):
          self.mock_audio_driver_player.delete.assert_called_once_with()
  
      def assert_driver_player_not_destroyed(self):
          self.assertFalse(self.mock_audio_driver_player.delete.called)
  
      def assert_silent_driver_player_destroyed(self):
          self.mock_silent_audio_driver_player.delete.assert_called_once_with()
  
      def assert_driver_player_started(self):
          self.mock_audio_driver_player.play.assert_called_once_with()
  
      def assert_driver_player_stopped(self):
          self.mock_audio_driver_player.stop.assert_called_once_with()
  
      def assert_driver_player_cleared(self):
          self.mock_audio_driver_player.clear.assert_called_once_with()
  
      def assert_source_seek(self, source, time):
          source._get_queue_source.return_value.seek.assert_called_once_with(time)
  
      def assert_new_texture_created(self, video_format):
          self.mock_texture_create.assert_called_once_with(video_format.width, video_format.height, rectangle=True)
  
      def assert_no_new_texture_created(self):
          self.assertFalse(self.mock_texture_create.called)
  
      def assert_texture_updated(self, frame_data):
          self.mock_texture.blit_into.assert_called_once_with(frame_data, 0, 0, 0)
  
      def assert_texture_not_updated(self):
          self.assertFalse(self.mock_texture.blit_into.called)
  
      def assert_update_texture_scheduled(self):
          self.mock_clock.schedule_once.assert_called_once_with(self.player.update_texture, 0)
  
      def assert_update_texture_unscheduled(self):
          self.mock_clock.unschedule.assert_called_with(self.player.update_texture)
  
      def pretend_player_at_time(self, t):
          self.player._mclock.set_time(t)
  
      def pretend_silent_driver_player_at_time(self, t):
          self.mock_silent_audio_driver_player.get_time.return_value = t
  
      def test_queue_single_audio_source_and_play(self):
          """Queue a single audio source and start playing it."""
  
          mock_source = self.create_mock_source(self.audio_format_1, None)
          self.player.queue(mock_source)
          self.assert_not_playing_yet(mock_source)
  
          self.player.play()
          self.assert_driver_player_created_for(mock_source)
          self.assert_driver_player_started()
          self.assert_now_playing(mock_source)
  
      def test_queue_multiple_audio_sources_same_format_and_play(self):
          """Queue multiple audio sources using the same audio format and start playing."""
          mock_source1 = self.create_mock_source(self.audio_format_1, None)
          mock_source2 = self.create_mock_source(self.audio_format_1, None)
          mock_source3 = self.create_mock_source(self.audio_format_1, None)
  
          self.player.queue(mock_source1)
          self.assert_not_playing_yet(mock_source1)
  
          self.player.queue(mock_source2)
          self.assert_not_playing_yet(mock_source1)
  
          self.player.queue(mock_source3)
          self.assert_not_playing_yet(mock_source1)
  
          self.player.play()
          self.assert_driver_player_created_for(mock_source1)
          self.assert_driver_player_started()
          self.assert_now_playing(mock_source1)
  
      def test_queue_multiple_audio_sources_different_format_and_play_and_skip(self):
          """Queue multiple audio sources having different formats and start playing. Different
          formats should be played by seperate driver players."""
          mock_source1 = self.create_mock_source(self.audio_format_1, None)
          mock_source2 = self.create_mock_source(self.audio_format_2, None)
          mock_source3 = self.create_mock_source(self.audio_format_3, None)
  
          self.player.queue(mock_source1)
          self.assert_not_playing_yet(mock_source1)
  
          self.player.queue(mock_source2)
          self.assert_not_playing_yet(mock_source1)
  
          self.player.queue(mock_source3)
          self.assert_not_playing_yet(mock_source1)
  
          self.player.play()
          self.assert_driver_player_created_for(mock_source1)
          self.assert_driver_player_started()
          self.assert_now_playing(mock_source1)
  
          self.reset_mocks()
          self.player.next_source()
          self.assert_driver_player_destroyed()
          self.assert_driver_player_created_for(mock_source2)
          self.assert_driver_player_started()
          self.assert_now_playing(mock_source2)
  
          self.reset_mocks()
          self.player.next_source()
          self.assert_driver_player_destroyed()
          self.assert_driver_player_created_for(mock_source3)
          self.assert_driver_player_started()
          self.assert_now_playing(mock_source3)
  
      def test_queue_multiple_audio_sources_same_format_and_play_and_skip(self):
          """When multiple audio sources with the same format are queued, they are played using the
          same driver player. Skipping to the next source is just advancing the source group.
          """
          mock_source1 = self.create_mock_source(self.audio_format_1, None)
          mock_source2 = self.create_mock_source(self.audio_format_1, None)
          mock_source3 = self.create_mock_source(self.audio_format_1, None)
  
          self.player.queue(mock_source1)
          self.player.queue(mock_source2)
          self.player.queue(mock_source3)
  
          self.player.play()
          self.assert_driver_player_created_for(mock_source1)
          self.assert_driver_player_started()
          self.assert_now_playing(mock_source1)
  
          self.reset_mocks()
          self.player.next_source()
          self.assert_driver_player_not_destroyed()
          self.assert_no_new_driver_player_created()
          self.assert_now_playing(mock_source2)
  
          self.reset_mocks()
          self.player.next_source()
          self.assert_driver_player_not_destroyed()
          self.assert_no_new_driver_player_created()
          self.assert_now_playing(mock_source3)
  
      def test_on_eos(self):
          """The player receives on_eos for every source, but does not need to do anything."""
          mock_source1 = self.create_mock_source(self.audio_format_1, None)
          mock_source2 = self.create_mock_source(self.audio_format_1, None)
          mock_source3 = self.create_mock_source(self.audio_format_1, None)
  
          self.player.queue(mock_source1)
          self.player.queue(mock_source2)
          self.player.queue(mock_source3)
  
          self.player.play()
          self.assert_driver_player_created_for(mock_source1)
          self.assert_driver_player_started()
  
          self.reset_mocks()
          self.player.dispatch_event('on_eos')
          self.assert_driver_player_not_destroyed()
  
      def test_player_stops_after_last_eos(self):
          """If the last playlist source is eos, the player stops."""
          mock_source = self.create_mock_source(self.audio_format_1, None)
          self.player.queue(mock_source)
          self.assert_not_playing_yet(mock_source)
  
          self.player.play()
          self.assert_driver_player_created_for(mock_source)
          self.assert_driver_player_started()
          self.assert_now_playing(mock_source)
  
          self.reset_mocks()
          self.player.dispatch_event('on_eos')
          self.assert_driver_player_destroyed()
          self.assert_not_playing(None)
  
      def test_eos_events(self):
          """Test receiving various eos events: on source eos, 
          on playlist exhausted and on player eos and on player next source.
          """
          on_eos_mock = mock.MagicMock(return_value=None)
          self.player.event('on_eos')(on_eos_mock)
          on_player_eos_mock = mock.MagicMock(return_value=None)
          self.player.event('on_player_eos')(on_player_eos_mock)
          on_player_next_source_mock = mock.MagicMock(return_value=None)
          self.player.event('on_player_next_source')(on_player_next_source_mock)
  
          def reset_eos_mocks():
              on_eos_mock.reset_mock()
              on_player_eos_mock.reset_mock()
              on_player_next_source_mock.reset_mock()
  
          def assert_eos_events_received(on_eos=False, on_player_eos=False, 
                                         on_player_next_source=False):
              self.assertEqual(on_eos_mock.called, on_eos)
              self.assertEqual(on_player_eos_mock.called, on_player_eos)
              self.assertEqual(on_player_next_source_mock.called, on_player_next_source)
  
          mock_source1 = self.create_mock_source(self.audio_format_1, None)
          mock_source2 = self.create_mock_source(self.audio_format_1, None)
          mock_source3 = self.create_mock_source(self.audio_format_2, None)
  
          self.player.queue(mock_source1)
          self.player.queue(mock_source2)
          self.player.queue(mock_source3)
          self.assert_not_playing_yet(mock_source1)
  
          self.player.play()
          self.assert_driver_player_created_for(mock_source1)
  
          self.reset_mocks()
          reset_eos_mocks()
          # Pretend the current source in the group was eos and next source started
          self.player.dispatch_event('on_eos')
          self.assert_driver_player_not_destroyed()
          assert_eos_events_received(on_eos=True, on_player_next_source=True)
          self.assertEqual(len(self.player._playlists), 2)
  
          # Pretend playlist is exhausted. Should be no more sources to play.
          self.player.dispatch_event('on_eos')
          self.reset_mocks()
          reset_eos_mocks()
          self.player.dispatch_event('on_eos')
          self.assert_not_playing(None)
          assert_eos_events_received(on_eos=True, on_player_eos=True)
  
      def test_pause_resume(self):
          """A stream can be paused. After that play will resume where paused."""
          mock_source = self.create_mock_source(self.audio_format_1, None)
          self.player.queue(mock_source)
          self.player.play()
          self.assert_driver_player_created_for(mock_source)
          self.assert_driver_player_started()
          self.assert_now_playing(mock_source)
  
          self.reset_mocks()
          self.pretend_player_at_time(0.5)
          self.player.pause()
          self.assert_driver_player_stopped()
          self.assert_driver_player_not_destroyed()
  
          self.reset_mocks()
          self.player.play()
          self.assertAlmostEqual(self.player.time, 0.5, places=2,
              msg='While playing, player should return time from driver player')
          self.assert_driver_player_started()
          self.assert_no_new_driver_player_created()
          self.assert_now_playing(mock_source)
  
      def test_delete(self):
          """Test clean up of the player when delete() is called."""
          mock_source1 = self.create_mock_source(self.audio_format_1, None)
          mock_source2 = self.create_mock_source(self.audio_format_2, None)
          mock_source3 = self.create_mock_source(self.audio_format_3, None)
  
          self.player.queue(mock_source1)
          self.player.queue(mock_source2)
          self.player.queue(mock_source3)
          self.assert_not_playing_yet(mock_source1)
  
          self.player.play()
          self.assert_driver_player_created_for(mock_source1)
          self.assert_driver_player_started()
  
          self.reset_mocks()
          self.pretend_player_at_time(1.)
          self.player.delete()
          self.assert_driver_player_destroyed()
  
      def test_empty_player(self):
          """A player without queued sources should not start a driver player and should not raise
          exceptions"""
          self.assert_not_playing_yet(None)
  
          self.reset_mocks()
          self.player.play()
          self.assert_no_new_driver_player_created()
  
          self.reset_mocks()
          self.player.pause()
          self.assert_no_new_driver_player_created()
          self.assert_driver_player_not_destroyed()
  
          self.reset_mocks()
          self.player.next_source()
          self.assert_no_new_driver_player_created()
          self.assert_driver_player_not_destroyed()
  
          self.reset_mocks()
          self.player.seek(0.8)
          self.assert_no_new_driver_player_created()
          self.assert_driver_player_not_destroyed()
  
          self.player.delete()
  
      def test_set_player_properties_before_playing(self):
          """When setting player properties before a driver specific player is 
          created, these settings should be propagated after creating the 
          player.
          """
          mock_source1 = self.create_mock_source(self.audio_format_1, None)
          mock_source2 = self.create_mock_source(self.audio_format_2, None)
          self.player.queue(mock_source1)
          self.player.queue(mock_source2)
          self.assert_not_playing_yet(mock_source1)
  
          self.reset_mocks()
          self.player.volume = 10.
          self.player.min_distance = 2.
          self.player.max_distance = 3.
          self.player.position = (4, 4, 4)
          self.player.pitch = 5.0
          self.player.cone_orientation = (6, 6, 6)
          self.player.cone_inner_angle = 7.
          self.player.cone_outer_angle = 8.
          self.player.cone_outer_gain = 9.
  
          def assert_properties_set():
              self.mock_audio_driver_player.set_volume.assert_called_once_with(10.)
              self.mock_audio_driver_player.set_min_distance.assert_called_once_with(2.)
              self.mock_audio_driver_player.set_max_distance.assert_called_once_with(3.)
              self.mock_audio_driver_player.set_position.assert_called_once_with((4, 4, 4))
              self.mock_audio_driver_player.set_pitch.assert_called_once_with(5.)
              self.mock_audio_driver_player.set_cone_orientation.assert_called_once_with((6, 6, 6))
              self.mock_audio_driver_player.set_cone_inner_angle.assert_called_once_with(7.)
              self.mock_audio_driver_player.set_cone_outer_angle.assert_called_once_with(8.)
              self.mock_audio_driver_player.set_cone_outer_gain.assert_called_once_with(9.)
  
          self.reset_mocks()
          self.player.play()
          self.assert_driver_player_created_for(mock_source1)
          self.assert_now_playing(mock_source1)
          assert_properties_set()
  
          self.reset_mocks()
          self.player.next_source()
          self.assert_driver_player_destroyed()
          self.assert_driver_player_created_for(mock_source2)
          assert_properties_set()
  
      def test_set_player_properties_while_playing(self):
          """When setting player properties while playing, the properties should 
          be propagated to the driver specific player right away."""
          mock_source1 = self.create_mock_source(self.audio_format_1, None)
          mock_source2 = self.create_mock_source(self.audio_format_2, None)
          self.player.queue(mock_source1)
          self.player.queue(mock_source2)
          self.assert_not_playing_yet(mock_source1)
  
          self.reset_mocks()
          self.player.play()
          self.assert_driver_player_created_for(mock_source1)
          self.assert_now_playing(mock_source1)
  
          self.reset_mocks()
          self.player.volume = 10.
          self.mock_audio_driver_player.set_volume.assert_called_once_with(10.)
  
          self.reset_mocks()
          self.player.min_distance = 2.
          self.mock_audio_driver_player.set_min_distance.assert_called_once_with(2.)
  
          self.reset_mocks()
          self.player.max_distance = 3.
          self.mock_audio_driver_player.set_max_distance.assert_called_once_with(3.)
  
          self.reset_mocks()
          self.player.position = (4, 4, 4)
          self.mock_audio_driver_player.set_position.assert_called_once_with((4, 4, 4))
  
          self.reset_mocks()
          self.player.pitch = 5.0
          self.mock_audio_driver_player.set_pitch.assert_called_once_with(5.)
  
          self.reset_mocks()
          self.player.cone_orientation = (6, 6, 6)
          self.mock_audio_driver_player.set_cone_orientation.assert_called_once_with((6, 6, 6))
  
          self.reset_mocks()
          self.player.cone_inner_angle = 7.
          self.mock_audio_driver_player.set_cone_inner_angle.assert_called_once_with(7.)
  
          self.reset_mocks()
          self.player.cone_outer_angle = 8.
          self.mock_audio_driver_player.set_cone_outer_angle.assert_called_once_with(8.)
  
          self.reset_mocks()
          self.player.cone_outer_gain = 9.
          self.mock_audio_driver_player.set_cone_outer_gain.assert_called_once_with(9.)
  
          self.reset_mocks()
          self.player.next_source()
          self.assert_driver_player_destroyed()
          self.assert_driver_player_created_for(mock_source2)
          self.mock_audio_driver_player.set_volume.assert_called_once_with(10.)
          self.mock_audio_driver_player.set_min_distance.assert_called_once_with(2.)
          self.mock_audio_driver_player.set_max_distance.assert_called_once_with(3.)
          self.mock_audio_driver_player.set_position.assert_called_once_with((4, 4, 4))
          self.mock_audio_driver_player.set_pitch.assert_called_once_with(5.)
          self.mock_audio_driver_player.set_cone_orientation.assert_called_once_with((6, 6, 6))
          self.mock_audio_driver_player.set_cone_inner_angle.assert_called_once_with(7.)
          self.mock_audio_driver_player.set_cone_outer_angle.assert_called_once_with(8.)
          self.mock_audio_driver_player.set_cone_outer_gain.assert_called_once_with(9.)
  
      def test_seek(self):
          """Test seeking to a specific time in the current source."""
          mock_source = self.create_mock_source(self.audio_format_1, None)
          self.player.queue(mock_source)
          self.assert_not_playing_yet(mock_source)
  
          self.reset_mocks()
          mock_source.reset_mock()
          self.player.seek(0.7)
          self.assert_source_seek(mock_source, 0.7)
  
          self.reset_mocks()
          mock_source.reset_mock()
          self.player.play()
          self.assert_driver_player_created_for(mock_source)
          self.assert_now_playing(mock_source)
  
          self.reset_mocks()
          mock_source.reset_mock()
          self.player.seek(0.2)
          self.assert_source_seek(mock_source, 0.2)
          # Clear buffers for immediate result
          self.assert_driver_player_cleared()
  
      def test_video_queue_and_play(self):
          """Sources can also include video. Instead of using a player to 
          continuously play the video, a texture is updated based on the
          video packet timestamp."""
          mock_source = self.create_mock_source(self.audio_format_1, self.video_format_1)
          self.set_video_data_for_mock_source(mock_source, [(0.2, 'a')])
          self.player.queue(mock_source)
          self.assert_not_playing_yet(mock_source)
  
          self.reset_mocks()
          self.player.play()
          self.assert_driver_player_created_for(mock_source)
          self.assert_driver_player_started()
          self.assert_now_playing(mock_source)
          self.assert_new_texture_created(self.video_format_1)
          self.assert_update_texture_scheduled()
  
          self.reset_mocks()
          self.pretend_player_at_time(0.2)
          self.player.update_texture()
          self.assert_texture_updated('a')
          self.assertIs(self.player.texture, self.mock_texture)
  
      def test_video_seek(self):
          """Sources with video can also be seeked. It's the Source 
          responsibility to present the Player with audio and video at the
          correct time."""
          mock_source = self.create_mock_source(self.audio_format_1, self.video_format_1)
          self.set_video_data_for_mock_source(mock_source, [(0.0, 'a'), (0.1, 'b'), (0.2, 'c'),
                                                            (0.3, 'd'), (0.4, 'e'), (0.5, 'f')])
          self.player.queue(mock_source)
          self.player.play()
          self.assert_new_texture_created(self.video_format_1)
          self.assert_update_texture_scheduled()
  
          self.reset_mocks()
          self.pretend_player_at_time(0.0)
          self.player.update_texture()
          self.assert_texture_updated('a')
  
          self.reset_mocks()
          self.player.seek(0.3)
          self.assert_source_seek(mock_source, 0.3)
          self.assert_no_new_texture_created()
          self.assert_texture_updated('d')
  
          self.reset_mocks()
          self.pretend_player_at_time(0.4)
          self.player.update_texture()
          self.assert_texture_updated('e')
  
      def test_video_frame_rate(self):
          """Videos texture are scheduled according to the video packet 
          timestamp."""
          mock_source1 = self.create_mock_source(self.audio_format_1, self.video_format_1)
          mock_source2 = self.create_mock_source(self.audio_format_1, self.video_format_2)
          for mock_source in (mock_source1, mock_source2):
              self.set_video_data_for_mock_source(
                  mock_source,
                  [(0.0, 'a'), (0.1, 'b'), (0.2, 'c'),
                   (0.3, 'd'), (0.4, 'e'), (0.5, 'f')]
              )
  
          self.player.queue(mock_source1)
          self.player.queue(mock_source2)
  
          self.player.play()
          self.assert_new_texture_created(self.video_format_1)
          self.assert_update_texture_scheduled()
  
          self.reset_mocks()
          self.player.next_source()
          self.assert_new_texture_created(self.video_format_2)
          self.assert_update_texture_unscheduled()
          # schedule_once called twice:
          #   - Once for seeking back to 0 the previous source in next_source()
          #   - Once for scheduling the next source update_texture
          assert self.mock_clock.schedule_once.call_count == 2
          self.mock_clock.schedule_once.assert_called_with(self.player.update_texture, 0)
  
      def test_video_seek_next_frame(self):
          """It is possible to jump directly to the next frame of video and adjust the audio player
          accordingly."""
          mock_source = self.create_mock_source(self.audio_format_1, self.video_format_1)
          self.set_video_data_for_mock_source(mock_source, [(0.0, 'a'), (0.2, 'b')])
          self.player.queue(mock_source)
          self.player.play()
          self.assert_new_texture_created(self.video_format_1)
          self.assert_update_texture_scheduled()
  
          self.reset_mocks()
          self.pretend_player_at_time(0.0)
          self.player.update_texture()
          self.assert_texture_updated('a')
  
          self.reset_mocks()
          self.player.seek_next_frame()
          self.assert_source_seek(mock_source, 0.2)
          self.assert_texture_updated('b')
  
      def test_video_runs_out_of_frames(self):
          """When the video runs out of frames, it stops updating the texture. The audio player is
          responsible for triggering eos."""
          mock_source = self.create_mock_source(self.audio_format_1, self.video_format_1)
          self.set_video_data_for_mock_source(mock_source, [(0.0, 'a'), (0.1, 'b')])
          self.player.queue(mock_source)
          self.player.play()
          self.assert_new_texture_created(self.video_format_1)
          self.assert_update_texture_scheduled()
  
          self.reset_mocks()
          self.pretend_player_at_time(0.0)
          self.player.update_texture()
          self.assert_texture_updated('a')
  
          self.reset_mocks()
          self.pretend_player_at_time(0.1)
          self.player.update_texture()
          self.assert_texture_updated('b')
  
          self.reset_mocks()
          self.pretend_player_at_time(0.2)
          self.player.update_texture()
          self.assert_texture_not_updated()
  
          self.reset_mocks()
          self.player.seek_next_frame()
          self.assert_texture_not_updated()
  
      def test_video_without_audio(self):
          """It is possible to have videos without audio streams."""
          mock_source = self.create_mock_source(None, self.video_format_1)
          self.player.queue(mock_source)
          self.player.play()
          self.assert_new_texture_created(self.video_format_1)
          self.assert_update_texture_scheduled()
          self.assert_no_new_driver_player_created()
  
  
  class PlayerGroupTestCase(FutureTestCase):
      def create_mock_player(self, has_audio=True):
          player = mock.MagicMock()
          if has_audio:
              audio_player = mock.PropertyMock(return_value=mock.MagicMock())
          else:
              audio_player = mock.PropertyMock(return_value=None)
          type(player)._audio_player = audio_player
          return player
  
      def assert_players_started(self, *players):
          for player in players:
              player.play.assert_called_once_with()
  
      def assert_audio_players_started(self, *players):
          # Find the one player that was used to start the group, the rest should not be used
          call_args = None
          audio_players = []
          for player in players:
              audio_player = player._audio_player
              audio_players.append(audio_player)
              if call_args is not None:
                  self.assertFalse(audio_player._play_group.called, msg='Only one player should be used to start the group')
              elif audio_player._play_group.called:
                  call_args = audio_player._play_group.call_args
  
          self.assertIsNotNone(call_args, msg='No player was used to start all audio players.')
          started_players = call_args[0][0]
          self.assertCountEqual(started_players, audio_players, msg='Not all players with audio players were started')
  
      def assert_players_stopped(self, *players):
          for player in players:
              player.pause.assert_called_once_with()
  
      def assert_audio_players_stopped(self, *players):
          # Find the one player that was used to start the group, the rest should not be used
          call_args = None
          audio_players = []
          for player in players:
              audio_player = player._audio_player
              audio_players.append(audio_player)
              if call_args is not None:
                  self.assertFalse(audio_player._stop_group.called, msg='Only one player should be used to stop the group')
              elif audio_player._stop_group.called:
                  call_args = audio_player._stop_group.call_args
  
          self.assertIsNotNone(call_args, msg='No player was used to stop all audio players.')
          stopped_players = call_args[0][0]
          self.assertCountEqual(stopped_players, audio_players, msg='Not all players with audio players were stopped')
  
      def reset_mocks(self, *mocks):
          for m in mocks:
              m.reset_mock()
  
      def test_empty_group(self):
          """Just check nothing explodes on an empty group."""
          group = PlayerGroup([])
          group.play()
          group.pause()
  
      def test_only_with_audio(self):
          """Test a group containing only players with audio."""
          players = [self.create_mock_player(has_audio=True) for _ in range(10)]
          group = PlayerGroup(players)
  
          group.play()
          self.assert_audio_players_started(*players)
          self.assert_players_started(*players)
          self.reset_mocks(*players)
  
          group.pause()
          self.assert_audio_players_stopped(*players)
          self.assert_players_stopped(*players)
  
      def test_only_without_audio(self):
          """Test a group containing only players without audio."""
          players = [self.create_mock_player(has_audio=False) for _ in range(10)]
          group = PlayerGroup(players)
  
          group.play()
          self.assert_players_started(*players)
          self.reset_mocks(*players)
  
          group.pause()
          self.assert_players_stopped(*players)
  
      def test_mixed_players(self):
          """Test a group containing both players with audio and players without audio."""
          players_with_audio = [self.create_mock_player(has_audio=True) for _ in range(10)]
          players_without_audio = [self.create_mock_player(has_audio=False) for _ in range(10)]
          players = players_with_audio + players_without_audio
          random.shuffle(players)
          group = PlayerGroup(players)
  
          group.play()
          self.assert_audio_players_started(*players_with_audio)
          self.assert_players_started(*players)
          self.reset_mocks(*players)
  
          group.pause()
          self.assert_audio_players_stopped(*players_with_audio)
          self.assert_players_stopped(*players)
  
  
files: tests/unit/media/test_player.py