Author: dmeyer
Date: Fri Nov 3 21:26:35 2006
New Revision: 1975
Modified:
trunk/popcorn/src/backends/base.py
trunk/popcorn/src/backends/mplayer/config.py
trunk/popcorn/src/backends/mplayer/player.py
trunk/popcorn/src/backends/xine/config.py
trunk/popcorn/src/backends/xine/player.py
trunk/popcorn/src/generic.py
trunk/popcorn/src/ptypes.py
Log:
o add STATE_OPEN between STATE_OPENING and STATE_PLAYING
o cleanup signal usage in backend player
o cleanup state handling
o move shared code to base
o rearange functions in backends to match base
o add header
o smaller cleanups
o broke gstreamer by doing the stuff above
Modified: trunk/popcorn/src/backends/base.py
==============================================================================
--- trunk/popcorn/src/backends/base.py (original)
+++ trunk/popcorn/src/backends/base.py Fri Nov 3 21:26:35 2006
@@ -28,6 +28,8 @@
# python imports
import sets
+import os
+import md5
# kaa imports
import kaa.notifier
@@ -41,32 +43,30 @@
Base class for players
"""
+ _instance_count = 0
+
def __init__(self):
self.signals = {
- "pause": kaa.notifier.Signal(),
- "play": kaa.notifier.Signal(),
- "pause_toggle": kaa.notifier.Signal(),
- "seek": kaa.notifier.Signal(),
- "start": kaa.notifier.Signal(),
- "failed": kaa.notifier.Signal(),
- # Stream ended (either stopped by user or finished)
- "end": kaa.notifier.Signal(),
"stream_changed": kaa.notifier.Signal(),
- "frame": kaa.notifier.Signal(), # CAP_CANVAS
- "osd_configure": kaa.notifier.Signal(), # CAP_OSD
-
- # Player released video and audio devices
- "release": kaa.notifier.Signal(),
-
- # Process died (shared memory will go away)
- "shm_quit": kaa.notifier.Signal()
+ "frame": kaa.notifier.Signal(),
+ "osd_configure": kaa.notifier.Signal(),
}
-
+ self._state_changed = kaa.notifier.Signal()
self._state_object = STATE_IDLE
self._window = None
self._size = None
self._config = None
+ self._instance_id = "popcorn-%d-%d" % (os.getpid(),
self._instance_count)
+ MediaPlayer._instance_count += 1
+ # shared memory keys
+ key = md5.md5(self._instance_id + "osd").hexdigest()[:7]
+ self._osd_shmkey = int(key, 16)
+ self._osd_shmem = None
+ key = md5.md5(self._instance_id + "frame").hexdigest()[:7]
+ self._frame_shmkey = int(key, 16)
+ self._frame_shmem = None
+
def get_capabilities(self):
"""
@@ -120,24 +120,7 @@
old_state = self._state_object
self._state_object = state
- if old_state == STATE_OPENING and state in (STATE_IDLE,
STATE_NOT_RUNNING):
- # From STATE_OPENING to not playing. This means something went
- # wrong and the player failed to play.
- self.signals["failed"].emit()
-
- if old_state == STATE_OPENING and \
- state in (STATE_PLAYING, STATE_PAUSED):
- # From STATE_OPENING to playing. Signal playback start
- self.signals["start"].emit()
-
- if old_state in (STATE_PLAYING, STATE_PAUSED) and \
- state in (STATE_IDLE, STATE_NOT_RUNNING):
- # From playing to finished. Signal end.
- self.signals["end"].emit()
-
- if state == STATE_NOT_RUNNING == self._state_object:
- self.signals["release"].emit()
- self.signals["shm_quit"].emit()
+ self._state_changed.emit(old_state, state)
# state property based on get_state and _set_state
_state = property(get_state, _set_state, None, 'state of the player')
@@ -236,7 +219,7 @@
"""
Release audio and video devices.
"""
- self.die()
+ pass
def seek_relative(self, offset):
Modified: trunk/popcorn/src/backends/mplayer/config.py
==============================================================================
--- trunk/popcorn/src/backends/mplayer/config.py (original)
+++ trunk/popcorn/src/backends/mplayer/config.py Fri Nov 3 21:26:35 2006
@@ -1,12 +1,45 @@
+# -*- coding: iso-8859-1 -*-
+# -----------------------------------------------------------------------------
+# mplayer/config.py - config for mplayer backend
+# -----------------------------------------------------------------------------
+# $Id$
+#
+# -----------------------------------------------------------------------------
+# kaa.popcorn - Generic Player API
+# Copyright (C) 2006 Jason Tackaberry, Dirk Meyer
+#
+# Please see the file AUTHORS for a complete list of authors.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
+# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# -----------------------------------------------------------------------------
+
+# python imports
import os
import sys
import gettext
+
+# kaa imports
from kaa.config import Var, Group, Dict, List
+# i18 setup for this file
path = os.path.normpath(
os.path.join(os.path.dirname(__file__), '../../../../share/locale'))
i18n = gettext.translation('kaa.popcorn', path, fallback=True).ugettext
+# config variable for mplayer
config = Group(desc=i18n('mplayer configuration'), schema=[
Var(name='activate', default=True, desc=i18n('activate backend'))
])
Modified: trunk/popcorn/src/backends/mplayer/player.py
==============================================================================
--- trunk/popcorn/src/backends/mplayer/player.py (original)
+++ trunk/popcorn/src/backends/mplayer/player.py Fri Nov 3 21:26:35 2006
@@ -1,15 +1,56 @@
+# -*- coding: iso-8859-1 -*-
+# -----------------------------------------------------------------------------
+# mplayer/player.py - mplayer backend
+# -----------------------------------------------------------------------------
+# $Id$
+#
+# -----------------------------------------------------------------------------
+# kaa.popcorn - Generic Player API
+# Copyright (C) 2006 Jason Tackaberry, Dirk Meyer
+#
+# Please see the file AUTHORS for a complete list of authors.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
+# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# -----------------------------------------------------------------------------
+
+# python imports
import logging
-import os, re, string, tempfile, time, stat, threading, md5, struct
-from kaa import notifier, display, shm
+import os
+import re
+import string
+import tempfile
+import stat
+import threading
+import md5
+import struct
+
+# kaa imports
import kaa
+from kaa import shm
import kaa.utils
+import kaa.notifier
+import kaa.display
+
+# kaa.popcorn base imports
from kaa.popcorn.backends.base import MediaPlayer
-from kaa.popcorn.ptypes import *
from kaa.popcorn.utils import parse_mrl
+from kaa.popcorn.ptypes import *
-# 0 = none, 1 = interesting lines, 2 = everything, 3 = everything + status,
-# 4 = everything + status + run through gdb
-DEBUG=1
+# start mplayer in gdb for debugging
+USE_GDB = False
BUFFER_UNLOCKED = 0x10
BUFFER_LOCKED = 0x20
@@ -49,7 +90,7 @@
if callback:
# We need to run MPlayer to get these values. Create a signal,
call
# ourself as a thread, and return the signal back to the caller.
- thread = notifier.Thread(_get_mplayer_info, path, None, mtime)
+ thread = kaa.notifier.Thread(_get_mplayer_info, path, None, mtime)
# Thread class ensures the callbacks get invoked in the main
thread.
thread.signals["completed"].connect(callback)
thread.signals["exception"].connect(callback)
@@ -97,37 +138,22 @@
-class MPlayerError(Exception):
- pass
-
-class MPlayerExitError(MPlayerError):
- pass
-
class MPlayer(MediaPlayer):
PATH = None
- _instance_count = 0
-
RE_STATUS = re.compile("V:\s*([\d+\.]+)|A:\s*([\d+\.]+)\s\W")
def __init__(self):
super(MPlayer, self).__init__()
+ self._state = STATE_NOT_RUNNING
self._mp_cmd = MPlayer.PATH
if not self._mp_cmd:
self._mp_cmd = kaa.utils.which("mplayer")
if not self._mp_cmd:
- raise MPlayerError, "No MPlayer executable found in PATH"
+ raise PlayerError, "No MPlayer executable found in PATH"
- self._debug = DEBUG
- # Used for vf_overlay and vf_outbuf
- self._instance_id = "%d-%d" % (os.getpid(), MPlayer._instance_count)
- MPlayer._instance_count += 1
-
- self._process = None
- self._state = STATE_NOT_RUNNING
- self._overlay_shmem = None
- self._outbuf_shmem = None
+ self._child_app = None
self._file = None
self._file_args = []
@@ -137,57 +163,16 @@
self._filters_add = []
self._last_line = None
- self.signals.update({
- "output": notifier.Signal(),
- })
-
self._mp_info = _get_mplayer_info(self._mp_cmd, self._handle_mp_info)
- self._check_new_frame_timer = notifier.WeakTimer(self._check_new_frame)
+ self._check_new_frame_timer =
kaa.notifier.WeakTimer(self._check_new_frame)
self._cur_outbuf_mode = [True, False, None] # vo, shmem, size
def __del__(self):
- if self._outbuf_shmem:
- self._outbuf_shmem.detach()
- if self._overlay_shmem:
- self._overlay_shmem.detach()
-
-
- def _spawn(self, args, hook_notifier = True):
- log.debug("Spawn: %s %s", self._mp_cmd, args)
-
- if self._debug > 3:
- # With debug > 3, run mplayer through gdb.
- self._process = notifier.Process("gdb")
- self._process.start(self._mp_cmd)
- self._process.write("run %s\n" % args)
- else:
- self._process = notifier.Process(self._mp_cmd)
- self._process.start(args)
-
- if hook_notifier:
- self._process.signals["stdout"].connect_weak(self._handle_line)
- self._process.signals["stderr"].connect_weak(self._handle_line)
- self._process.signals["completed"].connect_weak(self._exited)
-
self._process.set_stop_command(notifier.WeakCallback(self._end_child))
- return self._process
-
-
- def _make_dummy_input_config(self):
- """
- There is no way to make MPlayer ignore keys from the X11 window. So
- this hack makes a temp input file that maps all keys to a dummy (and
- non-existent) command which causes MPlayer not to react to any key
- presses, allowing us to implement our own handlers. The temp file is
- deleted once MPlayer has read it.
- """
- keys = filter(lambda x: x not in string.whitespace, string.printable)
- keys = list(keys) + self._mp_info["keylist"]
- fp, filename = tempfile.mkstemp()
- for key in keys:
- os.write(fp, "%s noop\n" % key)
- os.close(fp)
- return filename
+ if self._frame_shmem:
+ self._frame_shmem.detach()
+ if self._osd_shmem:
+ self._osd_shmem.detach()
def _handle_mp_info(self, info):
@@ -198,39 +183,44 @@
self._mp_info = info
- def _handle_line(self, line):
- if self._debug:
- if re.search("@@@|outbuf|overlay", line, re.I) and self._debug ==
1:
- print line
- elif line[:2] not in ("A:", "V:") and self._debug == 2:
- print line
- elif self._debug == 3:
- print line
+
+ #
+ # child IO
+ #
+
+ def _child_stop(self):
+ self._child_write("quit")
+ # Could be paused, try sending again.
+ self._child_write("quit")
+
+
+ def _child_handle_line(self, line):
+ if re.search("@@@|outbuf|overlay", line, re.I):
+ log.info(line)
+ elif line[:2] not in ("A:", "V:"):
+ log.debug(line)
+ elif USE_GDB:
+ log.debug(line)
if line.startswith("V:") or line.startswith("A:"):
m = MPlayer.RE_STATUS.search(line)
if m:
old_pos = self._position
self._position = float((m.group(1) or m.group(2)).replace(",",
"."))
- if self._position - old_pos < 0 or self._position - old_pos >
1:
- self.signals["seek"].emit(self._position)
+# if self._position - old_pos < 0 or self._position - old_pos
> 1:
+# self.signals["seek"].emit(self._position)
# XXX this logic won't work with seek-while-paused patch; state
# will be "playing" after a seek.
- if self._state == STATE_PAUSED:
- self.signals["pause_toggle"].emit()
if self._state not in (STATE_PAUSED, STATE_PLAYING):
self.set_frame_output_mode()
self._state = STATE_PLAYING
self.signals["stream_changed"].emit()
elif self._state != STATE_PLAYING:
self._state = STATE_PLAYING
- self.signals["play"].emit()
elif line.startswith(" ===== PAUSE"):
self._state = STATE_PAUSED
- self.signals["pause_toggle"].emit()
- self.signals["pause"].emit()
elif line.startswith("ID_") and line.find("=") != -1:
attr, value = line.split("=")
@@ -267,24 +257,25 @@
if m:
width, height = int(m.group(1)), int(m.group(2))
try:
- if self._overlay_shmem:
- self._overlay_shmem.detach()
+ if self._osd_shmem:
+ self._osd_shmem.detach()
except shm.error:
pass
- self._overlay_shmem =
shm.memory(shm.getshmid(self._get_overlay_shm_key()))
- self._overlay_shmem.attach()
+ self._osd_shmem = shm.memory(\
+ shm.getshmid(self._osd_shmkey))
+ self._osd_shmem.attach()
- self.signals["osd_configure"].emit(width, height,
self._overlay_shmem.addr + 16,
- width, height)
+ self.signals["osd_configure"].emit(\
+ width, height, self._osd_shmem.addr + 16, width, height)
elif line.startswith("outbuf:") and line.find("shmem key") != -1:
try:
- if self._outbuf_shmem:
- self._outbuf_shmem.detach()
+ if self._frame_shmem:
+ self._frame_shmem.detach()
except shm.error:
pass
- self._outbuf_shmem =
shm.memory(shm.getshmid(self._get_outbuf_shm_key()))
- self._outbuf_shmem.attach()
+ self._frame_shmem = shm.memory(shm.getshmid(self._frame_shmkey))
+ self._frame_shmem.attach()
self.set_frame_output_mode() # Sync
elif line.startswith("EOF code"):
@@ -296,53 +287,42 @@
file = line[line.find("file")+5:]
os.unlink(file)
- #elif line.startswith("File not found"):
- # file = line[line.find(":")+2:]
- # raise IOError, (2, "No such file or directory: %s" % file)
-
elif line.startswith("FATAL:"):
- raise MPlayerError, line.strip()
+ log.error(line.strip())
- elif self._debug > 3 and line.startswith("Program received signal
SIGSEGV"):
+ elif USE_GDB and line.startswith("Program received signal SIGSEGV"):
# Mplayer crashed, issue backtrace.
- self._process.write("thread apply all bt\n")
+ self._child_app.write("thread apply all bt\n")
if line.strip():
- self.signals["output"].emit(line)
self._last_line = line
- def _get_overlay_shm_key(self):
- name = "mplayer-overlay.%s" % self._instance_id
- return int(md5.md5(name).hexdigest()[:7], 16)
-
- def _get_outbuf_shm_key(self):
- name = "mplayer-outbuf.%s" % self._instance_id
- return int(md5.md5(name).hexdigest()[:7], 16)
-
-
- def _slave_cmd(self, cmd):
- if not self.is_alive():
+ def _child_write(self, cmd):
+ if not self._child_is_alive():
return False
+ log.info('mplayer send %s', cmd)
+ self._child_app.write(cmd + "\n")
- if self._debug >= 1:
- print "SLAVE:", cmd
- self._process.write(cmd + "\n")
-
- def _exited(self, exitcode):
- log.debug('mplayer exited')
+ def _child_exited(self, exitcode):
+ log.info('mplayer exited')
self._state = STATE_NOT_RUNNING
- # exit code is strange somehow
- # if exitcode != 0:
- # raise MPlayerExitError, (exitcode, self._last_line)
- def is_alive(self):
- return self._process and self._process.is_alive()
+ def _child_is_alive(self):
+ return self._child_app and self._child_app.is_alive()
+
- def open(self, mrl, user_args = None):
+ #
+ # Methods for MediaPlayer subclasses
+ #
+
+ def open(self, mrl):
+ """
+ Open mrl.
+ """
if self.get_state() != STATE_NOT_RUNNING:
print 'Error: not idle'
return False
@@ -369,22 +349,31 @@
else:
self._file = mrl
- if user_args:
- self._file_args.append(user_args)
+ self._state = STATE_OPENING
+
+ # We have a problem at this point. The 'open' function is used to
+ # open the stream and provide information about it. After that, the
+ # caller can still change stuff before calling play. But the current
+ # code uses -identify in mplayer on startup. This will give us the
+ # information just before starting. So we change from STATE_OPENING
+ # to STATE_OPEN just now and do not have information at this point.
+
+ self._state = STATE_OPEN
def play(self):
+ """
+ Start playback.
+ """
log.debug('mplayer start playback')
-
+
# we know that self._mp_info has to be there or the object would
# not be selected by the generic one. FIXME: verify that!
assert(self._mp_info)
- keyfile = self._make_dummy_input_config()
-
filters = self._filters_pre[:]
if 'outbuf' in self._mp_info['video_filters']:
- filters += ["outbuf=%s:yv12" % self._get_outbuf_shm_key()]
+ filters += ["outbuf=%s:yv12" % self._frame_shmkey]
filters += ["scale=%d:-2" % self._size[0], "expand=%d:%d" % self._size,
"dsize=%d:%d" % self._size ]
@@ -393,24 +382,37 @@
filters += self._filters_add
if 'overlay' in self._mp_info['video_filters']:
- filters += ["overlay=%s" % self._get_overlay_shm_key()]
+ filters += ["overlay=%s" % self._osd_shmkey]
args = [ "-v", "-slave", "-osdlevel", "0", "-nolirc", "-nojoystick", \
"-nomouseinput", "-nodouble", "-fixed-vo", "-identify", \
"-framedrop", "-vf", ",".join(filters) ]
- if isinstance(self._window, display.X11Window):
+ if isinstance(self._window, kaa.display.X11Window):
args.extend((
"-wid", hex(self._window.get_id()),
"-display", self._window.get_display().get_string(),
- "-input", "conf=%s" % keyfile))
+ "-input"))
else:
# no window == no video out
args.extend(('-vo', 'null'))
-
+
# FIXME, add more settings here
args += [ '-ao', self._config.audio.driver ]
-
+
+ # There is no way to make MPlayer ignore keys from the X11 window. So
+ # this hack makes a temp input file that maps all keys to a dummy (and
+ # non-existent) command which causes MPlayer not to react to any key
+ # presses, allowing us to implement our own handlers. The temp file is
+ # deleted once MPlayer has read it.
+ keys = filter(lambda x: x not in string.whitespace, string.printable)
+ keys = list(keys) + self._mp_info["keylist"]
+ fp, keyfile = tempfile.mkstemp()
+ for key in keys:
+ os.write(fp, "%s noop\n" % key)
+ os.close(fp)
+ args.append("conf=%s" % keyfile)
+
if self._file_args:
if isinstance(self._file_args, str):
args.extend(self._file_args.split(' '))
@@ -420,59 +422,123 @@
if self._file:
args.append(self._file)
- self._spawn(args)
- self._state = STATE_OPENING
+ log.info("spawn: %s %s", self._mp_cmd, args)
+ if USE_GDB:
+ self._child_app = kaa.notifier.Process("gdb")
+ self._child_app.start(self._mp_cmd)
+ self._child_app.write("run %s\n" % args)
+ else:
+ self._child_app = kaa.notifier.Process(self._mp_cmd)
+ self._child_app.start(args)
+
+ self._child_app.signals["stdout"].connect_weak(self._child_handle_line)
+ self._child_app.signals["stderr"].connect_weak(self._child_handle_line)
+ self._child_app.signals["completed"].connect_weak(self._child_exited)
+
self._child_app.set_stop_command(kaa.notifier.WeakCallback(self._child_stop))
return
+ def stop(self):
+ """
+ Stop playback.
+ """
+ if self._child_app:
+ self._child_app.stop()
+ self._state = STATE_SHUTDOWN
+
+
def pause(self):
- if self.get_state() == STATE_PLAYING:
- self._slave_cmd("pause")
+ """
+ Pause playback.
+ """
+ self._child_write("pause")
def resume(self):
- if self.get_state() == STATE_PAUSED:
- self._slave_cmd("pause")
+ """
+ Resume playback.
+ """
+ self._child_write("pause")
+
def seek(self, value, type):
+ """
+ Seek. Possible types are SEEK_RELATIVE, SEEK_ABSOLUTE and
SEEK_PERCENTAGE.
+ """
s = [SEEK_RELATIVE, SEEK_PERCENTAGE, SEEK_ABSOLUTE]
- self._slave_cmd("seek %f %s" % (value, s.index(type)))
-
- def stop(self):
- self.die()
- self._state = STATE_SHUTDOWN
-
- def _end_child(self):
- self._slave_cmd("quit")
- # Could be paused, try sending again.
- self._slave_cmd("quit")
+ self._child_write("seek %f %s" % (value, s.index(type)))
- def die(self):
- if self._process:
- self._process.stop()
def get_position(self):
+ """
+ Get current playing position.
+ """
return self._position
+
def get_info(self):
+ """
+ Get information about the stream.
+ """
return self._file_info
+
+ #
+ # Methods for filter handling (not yet in generic and base
+ #
+
def prepend_filter(self, filter):
+ """
+ Add filter to the prepend list.
+ """
self._filters_pre.append(filter)
+
def append_filter(self, filter):
+ """
+ Add filter to the normal filter list.
+ """
self._filters_add.append(filter)
+
def get_filters(self):
+ """
+ Return all filter set.
+ """
return self._filters_pre + self._filters_add
+
def remove_filter(self, filter):
+ """
+ Remove filter for filter list.
+ """
for l in (self._filters_pre, self._filters_add):
if filter in l:
l.remove(filter)
+
+ #
+ # Methods and helper for MediaPlayer subclasses for CAP_OSD
+ #
+
+ def osd_can_update(self):
+ """
+ Returns True if it is safe to write to the player's shared memory
+ buffer used for OSD, and False otherwise. If this buffer is written
+ to even though this function returns False, the OSD may exhibit
+ corrupt output or tearing during animations.
+ See generic.osd_can_update for details.
+ """
+ if not self._osd_shmem:
+ return False
+
+
def osd_update(self, alpha = None, visible = None, invalid_regions = None):
+ """
+ Updates the OSD of the player based on the given argments:
+ See generic.osd_update for details.
+ """
cmd = []
if alpha != None:
cmd.append("alpha=%d" % alpha)
@@ -481,59 +547,40 @@
if invalid_regions:
for (x, y, w, h) in invalid_regions:
cmd.append("invalidate=%d:%d:%d:%d" % (x, y, w, h))
- self._slave_cmd("overlay %s" % ",".join(cmd))
+ self._child_write("overlay %s" % ",".join(cmd))
self._overlay_set_lock(BUFFER_LOCKED)
-
- def osd_can_update(self):
- if not self._overlay_shmem:
- return False
-
try:
- if ord(self._overlay_shmem.read(1)) == BUFFER_UNLOCKED:
+ if ord(self._osd_shmem.read(1)) == BUFFER_UNLOCKED:
return True
except shm.error:
- self._overlay_shmem.detach()
- self._overlay_shmem = None
+ self._osd_shmem.detach()
+ self._osd_shmem = None
return False
def _overlay_set_lock(self, byte):
try:
- if self._overlay_shmem and self._overlay_shmem.attached:
- self._overlay_shmem.write(chr(byte))
+ if self._osd_shmem and self._osd_shmem.attached:
+ self._osd_shmem.write(chr(byte))
except shm.error:
- self._overlay_shmem.detach()
- self._overlay_shmem = None
+ self._osd_shmem.detach()
+ self._osd_shmem = None
- def _check_new_frame(self):
- if not self._outbuf_shmem:
- return
- try:
- lock, width, height, aspect = struct.unpack("hhhd",
self._outbuf_shmem.read(16))
- except shm.error:
- self._outbuf_shmem.detach()
- self._outbuf_shmem = None
- return
-
- if lock & BUFFER_UNLOCKED:
- return
-
- if width > 0 and height > 0 and aspect > 0:
- self.signals["frame"].emit(width, height, aspect,
self._outbuf_shmem.addr + 16, "yv12")
-
- def unlock_frame_buffer(self):
- try:
- self._outbuf_shmem.write(chr(BUFFER_UNLOCKED))
- except shm.error:
- self._outbuf_shmem.detach()
- self._outbuf_shmem = None
+ #
+ # Methods and helper for MediaPlayer subclasses for CAP_CANVAS
+ #
def set_frame_output_mode(self, vo = None, notify = None, size = None):
+ """
+ Controls if and how frames are delivered via the 'frame' signal, and
+ whether or not frames are drawn to the vo driver's video window.
+ See generic.set_frame_output_mode for details.
+ """
if vo != None:
self._cur_outbuf_mode[0] = vo
if notify != None:
@@ -545,7 +592,7 @@
if size != None:
self._cur_outbuf_mode[2] = size
- if not self.is_alive():
+ if not self._child_is_alive():
return
mode = { (False, False): 0, (True, False): 1,
@@ -553,10 +600,38 @@
size = self._cur_outbuf_mode[2]
if size == None:
- self._slave_cmd("outbuf %d" % mode)
+ self._child_write("outbuf %d" % mode)
else:
- self._slave_cmd("outbuf %d %d %d" % (mode, size[0], size[1]))
+ self._child_write("outbuf %d %d %d" % (mode, size[0], size[1]))
- def nav_command(self, input):
- # MPlayer has no dvdnav support (yet).
- return False
+
+ def unlock_frame_buffer(self):
+ """
+ Unlocks the frame buffer provided by the last 'frame' signal
+ See generic.unlock_frame_buffer for details.
+ """
+ try:
+ self._frame_shmem.write(chr(BUFFER_UNLOCKED))
+ except shm.error:
+ self._frame_shmem.detach()
+ self._frame_shmem = None
+
+
+ def _check_new_frame(self):
+ if not self._frame_shmem:
+ return
+
+ try:
+ lock, width, height, aspect = \
+ struct.unpack("hhhd", self._frame_shmem.read(16))
+ except shm.error:
+ self._frame_shmem.detach()
+ self._frame_shmem = None
+ return
+
+ if lock & BUFFER_UNLOCKED:
+ return
+
+ if width > 0 and height > 0 and aspect > 0:
+ self.signals["frame"].emit(\
+ width, height, aspect, self._frame_shmem.addr + 16, "yv12")
Modified: trunk/popcorn/src/backends/xine/config.py
==============================================================================
--- trunk/popcorn/src/backends/xine/config.py (original)
+++ trunk/popcorn/src/backends/xine/config.py Fri Nov 3 21:26:35 2006
@@ -1,12 +1,45 @@
+# -*- coding: iso-8859-1 -*-
+# -----------------------------------------------------------------------------
+# xine/config.py - config for xine backend
+# -----------------------------------------------------------------------------
+# $Id$
+#
+# -----------------------------------------------------------------------------
+# kaa.popcorn - Generic Player API
+# Copyright (C) 2006 Jason Tackaberry, Dirk Meyer
+#
+# Please see the file AUTHORS for a complete list of authors.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
+# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# -----------------------------------------------------------------------------
+
+# python imports
import os
import sys
import gettext
+
+# kaa imports
from kaa.config import Var, Group, Dict, List
+# i18 setup for this file
path = os.path.normpath(
os.path.join(os.path.dirname(__file__), '../../../../share/locale'))
i18n = gettext.translation('kaa.popcorn', path, fallback=True).ugettext
+# config variable for xine
config = Group(desc=i18n('xine configuration'), schema=[
Var(name='activate', default=True, desc=i18n('activate backend'))
])
Modified: trunk/popcorn/src/backends/xine/player.py
==============================================================================
--- trunk/popcorn/src/backends/xine/player.py (original)
+++ trunk/popcorn/src/backends/xine/player.py Fri Nov 3 21:26:35 2006
@@ -1,17 +1,48 @@
+# -*- coding: iso-8859-1 -*-
+# -----------------------------------------------------------------------------
+# xine/player.py - xine backend
+# -----------------------------------------------------------------------------
+# $Id$
+#
+# -----------------------------------------------------------------------------
+# kaa.popcorn - Generic Player API
+# Copyright (C) 2006 Jason Tackaberry, Dirk Meyer
+#
+# Please see the file AUTHORS for a complete list of authors.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
+# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# -----------------------------------------------------------------------------
+
+# python imports
import sys
import os
import md5
import struct
import logging
+# kaa imports
import kaa
import kaa.notifier
import kaa.shm
import kaa.xine as xine
+# kaa.popcorn imports
from kaa.popcorn.backends.base import MediaPlayer
-from kaa.popcorn.ptypes import *
from kaa.popcorn.utils import ChildProcess, parse_mrl
+from kaa.popcorn.ptypes import *
# get logging object
log = logging.getLogger('popcorn.xine')
@@ -21,29 +52,34 @@
class Xine(MediaPlayer):
- _instance_count = 0
-
def __init__(self):
super(Xine, self).__init__()
- self._instance_id = "kaaxine-%d-%d" % (os.getpid(),
Xine._instance_count)
- Xine._instance_count += 1
-
- self._osd_shmkey = int(md5.md5(self._instance_id +
"osd").hexdigest()[:7], 16)
- self._frame_shmkey = int(md5.md5(self._instance_id +
"frame").hexdigest()[:7], 16)
- self._osd_shmem = self._frame_shmem = None
- #kaa.signals["shutdown"].connect_weak(self._remove_shmem)
self._check_new_frame_timer =
kaa.notifier.WeakTimer(self._check_new_frame)
-
self._is_in_menu = False
- self._state_data = None
self._stream_info = {}
self._position = 0.0
self._cur_frame_output_mode = [True, False, None] # vo, shmem, size
+ self._child_spawn()
+
+
+ #
+ # child handling
+ #
+
+ def _child_spawn(self):
+ # Launch self (-u is unbuffered stdout)
+ script = os.path.join(os.path.dirname(__file__), 'main.py')
+ self._xine = ChildProcess(self, script, str(self._instance_id))
+ self._xine.signals["completed"].connect_weak(self._child_exited)
+ self._xine.set_stop_command(kaa.notifier.WeakCallback(self._xine.die))
+ self._xine.start()
- self._spawn()
+ def _child_exited(self, exitcode):
+ log.debug('xine child dead')
+ self._xine = None
- def _remove_shmem(self):
+ # remove shared memory
if self._osd_shmem:
try:
self._osd_shmem.detach()
@@ -60,33 +96,12 @@
except kaa.shm.error:
pass
self._frame_shmem = None
-
-
- def _spawn(self):
- # Launch self (-u is unbuffered stdout)
- script = os.path.join(os.path.dirname(__file__), 'main.py')
- self._xine = ChildProcess(self, script, str(self._instance_id))
- self._xine.signals["completed"].connect_weak(self._exited)
- self._xine.set_stop_command(kaa.notifier.WeakCallback(self._end_child))
- self._xine.start()
-
-
-
- def _end_child(self):
- self._state = STATE_SHUTDOWN
- self._xine.die()
-
-
- def _exited(self, exitcode):
- log.debug('xine child dead')
- self._xine = None
- self._remove_shmem()
self._state = STATE_NOT_RUNNING
- #
#############################################################################
+ #
# Commands from child
- #
#############################################################################
+ #
def _child_set_status(self, pos, time, length, status, speed):
old_pos = self._position
@@ -96,20 +111,14 @@
if status == 2:
if self.get_state() not in (STATE_PAUSED, STATE_PLAYING):
self._state = STATE_PLAYING
- self.signals["play"].emit()
if speed == xine.SPEED_PAUSE and self.get_state() != STATE_PAUSED:
self._state = STATE_PAUSED
- self.signals["pause_toggle"].emit()
- self.signals["pause"].emit()
elif speed > xine.SPEED_PAUSE and self.get_state() !=
STATE_PLAYING:
prev_state = self.get_state()
self._state = STATE_PLAYING
- if prev_state == STATE_PAUSED:
- self.signals["pause_toggle"].emit()
- self.signals["play"].emit()
-
- if self._position - old_pos < 0 or self._position - old_pos > 1:
- self.signals["seek"].emit(self._position)
+ # TODO:
+ # if self._position - old_pos < 0 or self._position - old_pos > 1:
+ # self.signals["seek"].emit(self._position)
elif status in (0, 1):
if self.get_state() in (STATE_PAUSED, STATE_PLAYING):
# Stream ended.
@@ -131,8 +140,8 @@
# TODO: remember these values and emit them to new connections to
# this signal after this point.
- self.signals["osd_configure"].emit(width, height, self._osd_shmem.addr
+ 16,
- width, height)
+ self.signals["osd_configure"].emit(\
+ width, height, self._osd_shmem.addr + 16, width, height)
def _child_resize(self, size):
@@ -150,8 +159,7 @@
self._stream_info = info
if self._state == STATE_OPENING:
- self._xine.play()
- self.set_frame_output_mode()
+ self._state = STATE_OPEN
if changed:
self.signals["stream_changed"].emit()
@@ -167,10 +175,9 @@
self._state = STATE_IDLE
- #
#############################################################################
+ #
# Window handling
- #
#############################################################################
-
+ #
def _window_visibility_event(self):
self._xine.window_changed(self._window.get_id(),
self._window.get_size(),
@@ -184,24 +191,45 @@
self._xine.window_changed(self._window.get_id(), size,
self._window.get_visible(), [])
+ def set_window(self, window):
+ """
+ Set a window for the player (override from MediaPlayer)
+ """
+ old_window = self._window
+ super(Xine, self).set_window(window)
+ if old_window and old_window != self._window:
+ # Disconnect signals from existing window.
+
old_window.signals["configure_event"].disconnect(self._window_configure_event)
+
old_window.signals["map_event"].disconnect(self._window_visibility_event)
+
old_window.signals["unmap_event"].disconnect(self._window_visibility_event)
+
old_window.signals["expose_event"].disconnect(self._window_expose_event)
+
+ if window and window.signals and old_window != self._window:
+
window.signals["configure_event"].connect_weak(self._window_configure_event)
+
window.signals["map_event"].connect_weak(self._window_visibility_event)
+
window.signals["unmap_event"].connect_weak(self._window_visibility_event)
+
window.signals["expose_event"].connect_weak(self._window_expose_event)
+
+ # Sends a window_changed command to slave.
+ if window:
+ self._window_visibility_event()
- #
#############################################################################
- # API exposed to generic player
- #
#############################################################################
+ #
+ # Methods for MediaPlayer subclasses
+ #
def open(self, mrl):
+ """
+ Open mrl.
+ """
scheme, path = parse_mrl(mrl)
if scheme not in self.get_supported_schemes():
raise ValueError, "Unsupported mrl scheme '%s'" % scheme
-
self._mrl = "%s:%s" % (scheme, path)
-
-
- def play(self):
if not self._xine:
- self._spawn()
+ self._child_spawn()
wid = None
if self._window:
@@ -209,43 +237,126 @@
self._xine.setup(wid=wid)
self._position = 0.0
- log.debug('xine play')
+ log.debug('xine open')
self._xine.open(self._mrl)
self._state = STATE_OPENING
+ def play(self):
+ """
+ Start playback.
+ """
+ log.debug('play')
+ self._xine.play()
+ self.set_frame_output_mode()
+
+
+ def stop(self):
+ """
+ Stop playback.
+ """
+ log.debug('xine stop')
+ self._xine.stop()
+
+
def pause(self):
+ """
+ Pause playback.
+ """
self._xine.pause()
def resume(self):
+ """
+ Resume playback.
+ """
self._xine.resume()
- def stop(self):
- log.debug('xine stop')
- self._xine.stop()
-
-
- def die(self):
+ def release(self):
+ """
+ Release audio and video devices.
+ """
if self._xine:
self._state = STATE_SHUTDOWN
self._xine.die()
def seek(self, value, type):
+ """
+ Seek. Possible types are SEEK_RELATIVE, SEEK_ABSOLUTE and
SEEK_PERCENTAGE.
+ """
self._xine.seek(value, type)
+ def get_position(self):
+ """
+ Get current playing position.
+ """
+ return self._position
+
+
def get_info(self):
+ """
+ Get information about the stream.
+ """
return self._stream_info
- def osd_update(self, alpha = None, visible = None, invalid_regions = None):
- self._xine.osd_update(alpha, visible, invalid_regions)
+ def nav_command(self, input):
+ """
+ Issue the navigation command to the player.
+ """
+ map = {
+ "up": xine.EVENT_INPUT_UP,
+ "down": xine.EVENT_INPUT_DOWN,
+ "left": xine.EVENT_INPUT_LEFT,
+ "right": xine.EVENT_INPUT_RIGHT,
+ "select": xine.EVENT_INPUT_SELECT,
+ "prev": xine.EVENT_INPUT_PREVIOUS,
+ "next": xine.EVENT_INPUT_NEXT,
+ "angle_prev": xine.EVENT_INPUT_ANGLE_PREVIOUS,
+ "angle_next": xine.EVENT_INPUT_ANGLE_NEXT,
+ "menu1": xine.EVENT_INPUT_MENU1,
+ "menu2": xine.EVENT_INPUT_MENU2,
+ "menu3": xine.EVENT_INPUT_MENU3,
+ "menu4": xine.EVENT_INPUT_MENU4,
+ "0": xine.EVENT_INPUT_NUMBER_0,
+ "1": xine.EVENT_INPUT_NUMBER_1,
+ "2": xine.EVENT_INPUT_NUMBER_2,
+ "3": xine.EVENT_INPUT_NUMBER_3,
+ "4": xine.EVENT_INPUT_NUMBER_4,
+ "5": xine.EVENT_INPUT_NUMBER_5,
+ "6": xine.EVENT_INPUT_NUMBER_6,
+ "7": xine.EVENT_INPUT_NUMBER_7,
+ "8": xine.EVENT_INPUT_NUMBER_8,
+ "9": xine.EVENT_INPUT_NUMBER_9
+ }
+ if input in map:
+ self._xine.input(map[input])
+ return True
+ return False
+
+
+ def is_in_menu(self):
+ """
+ Return True if the player is in a navigation menu.
+ """
+ return self._is_in_menu
+ #
+ # Methods and helper for MediaPlayer subclasses for CAP_OSD
+ #
+
def osd_can_update(self):
+ """
+ Returns True if it is safe to write to the player's shared memory
+ buffer used for OSD, and False otherwise. If this buffer is written
+ to even though this function returns False, the OSD may exhibit
+ corrupt output or tearing during animations.
+ See generic.osd_can_update for details.
+ """
if not self._osd_shmem:
return False
@@ -258,7 +369,26 @@
return False
+
+ def osd_update(self, alpha = None, visible = None, invalid_regions = None):
+ """
+ Updates the OSD of the player based on the given argments:
+ See generic.osd_update for details.
+ """
+ self._xine.osd_update(alpha, visible, invalid_regions)
+
+
+
+ #
+ # Methods and helper for MediaPlayer subclasses for CAP_CANVAS
+ #
+
def set_frame_output_mode(self, vo = None, notify = None, size = None):
+ """
+ Controls if and how frames are delivered via the 'frame' signal, and
+ whether or not frames are drawn to the vo driver's video window.
+ See generic.set_frame_output_mode for details.
+ """
if vo != None:
self._cur_frame_output_mode[0] = vo
if notify != None:
@@ -279,37 +409,25 @@
self._xine.frame_output(vo, notify, size)
- def set_window(self, window):
- old_window = self._window
- super(Xine, self).set_window(window)
-
- if old_window and old_window != self._window:
- # Disconnect signals from existing window.
-
old_window.signals["configure_event"].disconnect(self._window_configure_event)
-
old_window.signals["map_event"].disconnect(self._window_visibility_event)
-
old_window.signals["unmap_event"].disconnect(self._window_visibility_event)
-
old_window.signals["expose_event"].disconnect(self._window_expose_event)
-
- if window and window.signals and old_window != self._window:
-
window.signals["configure_event"].connect_weak(self._window_configure_event)
-
window.signals["map_event"].connect_weak(self._window_visibility_event)
-
window.signals["unmap_event"].connect_weak(self._window_visibility_event)
-
window.signals["expose_event"].connect_weak(self._window_expose_event)
-
- # Sends a window_changed command to slave.
- if window:
- self._window_visibility_event()
+ def unlock_frame_buffer(self):
+ """
+ Unlocks the frame buffer provided by the last 'frame' signal
+ See generic.unlock_frame_buffer for details.
+ """
+ try:
+ self._frame_shmem.write(chr(BUFFER_UNLOCKED))
+ except kaa.shm.error:
+ self._frame_shmem.detach()
+ self._frame_shmem = None
- def get_position(self):
- return self._position
-
def _check_new_frame(self):
if not self._frame_shmem:
return
try:
- lock, width, height, aspect = struct.unpack("hhhd",
self._frame_shmem.read(16))
+ lock, width, height, aspect = struct.unpack(\
+ "hhhd", self._frame_shmem.read(16))
except kaa.shm.error:
self._frame_shmem.detach()
self._frame_shmem = None
@@ -319,48 +437,5 @@
return
if width > 0 and height > 0 and aspect > 0:
- self.signals["frame"].emit(width, height, aspect,
self._frame_shmem.addr + 16, "bgr32")
-
-
- def unlock_frame_buffer(self):
- try:
- self._frame_shmem.write(chr(BUFFER_UNLOCKED))
- except kaa.shm.error:
- self._frame_shmem.detach()
- self._frame_shmem = None
-
-
- def nav_command(self, input):
- map = {
- "up": xine.EVENT_INPUT_UP,
- "down": xine.EVENT_INPUT_DOWN,
- "left": xine.EVENT_INPUT_LEFT,
- "right": xine.EVENT_INPUT_RIGHT,
- "select": xine.EVENT_INPUT_SELECT,
- "prev": xine.EVENT_INPUT_PREVIOUS,
- "next": xine.EVENT_INPUT_NEXT,
- "angle_prev": xine.EVENT_INPUT_ANGLE_PREVIOUS,
- "angle_next": xine.EVENT_INPUT_ANGLE_NEXT,
- "menu1": xine.EVENT_INPUT_MENU1,
- "menu2": xine.EVENT_INPUT_MENU2,
- "menu3": xine.EVENT_INPUT_MENU3,
- "menu4": xine.EVENT_INPUT_MENU4,
- "0": xine.EVENT_INPUT_NUMBER_0,
- "1": xine.EVENT_INPUT_NUMBER_1,
- "2": xine.EVENT_INPUT_NUMBER_2,
- "3": xine.EVENT_INPUT_NUMBER_3,
- "4": xine.EVENT_INPUT_NUMBER_4,
- "5": xine.EVENT_INPUT_NUMBER_5,
- "6": xine.EVENT_INPUT_NUMBER_6,
- "7": xine.EVENT_INPUT_NUMBER_7,
- "8": xine.EVENT_INPUT_NUMBER_8,
- "9": xine.EVENT_INPUT_NUMBER_9
- }
- if input in map:
- self._xine.input(map[input])
- return True
- return False
-
-
- def is_in_menu(self):
- return self._is_in_menu
+ a = self._frame_shmem.addr + 16
+ self.signals["frame"].emit(width, height, aspect, a, "bgr32")
Modified: trunk/popcorn/src/generic.py
==============================================================================
--- trunk/popcorn/src/generic.py (original)
+++ trunk/popcorn/src/generic.py Fri Nov 3 21:26:35 2006
@@ -89,14 +89,17 @@
# signals created by this class
"open": kaa.notifier.Signal(),
"start": kaa.notifier.Signal(),
- "failed": kaa.notifier.Signal(),
+ "play": kaa.notifier.Signal(),
"end": kaa.notifier.Signal(),
-
- # pass thru signals from player
+ "failed": kaa.notifier.Signal(),
"pause": kaa.notifier.Signal(),
- "play": kaa.notifier.Signal(),
"pause_toggle": kaa.notifier.Signal(),
- "seek": kaa.notifier.Signal(),
+
+ # "seek": kaa.notifier.Signal(),
+ # Process died (shared memory will go away)
+ # "shm_quit": kaa.notifier.Signal()
+
+ # pass thru signals from player
"stream_changed": kaa.notifier.Signal(),
# Emitted when a new frame is availabnle. See
# set_frame_output_mode() doc for more info.
@@ -104,8 +107,6 @@
# Emitted when OSD buffer has changed. See osd_update() doc
# for more info.
"osd_configure": kaa.notifier.Signal(), # CAP_OSD
- # Process died (shared memory will go away)
- "shm_quit": kaa.notifier.Signal()
}
# pending commands
@@ -130,20 +131,21 @@
self._open_mrl, self._open_caps, player, exclude)
- def _state_change(self, signal):
+ def _state_change(self, old_state, state):
"""
- The used player changed its state and emited a signal. This function
- will emit the same signal to the application, handles some internal
- changes and will call the pending calls based on the new state.
"""
- log.debug('player signal: %s', signal)
-
- if signal == 'failed':
- # The playing has failed. This means that the player we wanted to
- # use was unable to play this file. In that case we add the player
- # the list of of failed_player and try to find a better one.
- # TODO: What happens if the user tries to open a new file while
- # we are trying to find a good player for the old mrl?
+ log.debug('player %s -> %s', old_state, state)
+
+ if old_state in (STATE_OPENING, STATE_OPEN) and \
+ state in (STATE_IDLE, STATE_NOT_RUNNING, STATE_SHUTDOWN):
+ # From STATE_OPEN(ING) to not playing. This means something
+ # went wrong and the player failed to play. This means
+ # that the player we wanted to use was unable to play this
+ # file. In that case we add the player the list of of
+ # failed_player and try to find a better one.
+ # TODO: What happens if the user tries to open a new file
+ # while we are trying to find a good player for the old
+ # mrl?
self._failed_player.append(self.get_player_id())
self._pending = []
self._player.release()
@@ -153,23 +155,44 @@
self._create_player(cls)
self._open(self._open_mrl)
self.play()
- return
+ return True
+ # everything failed
+ self.signals["failed"].emit()
+
+ if old_state == STATE_OPENING and state == STATE_OPEN:
+ # stream open now for playing
+ print self._player.get_info()
+ self.signals["open"].emit(self._player.get_info())
- if signal in self.signals:
- # signal the change
- self.signals[signal].emit()
-
- if signal in ('end', 'failed') and self.get_state() == STATE_IDLE \
- and not self._pending:
- # no new mrl to play, release player
- log.info('release player')
- return self._player.release()
-
- if signal == 'release':
+ if old_state == STATE_OPEN and \
+ state in (STATE_PLAYING, STATE_PAUSED):
+ # From STATE_OPENING to playing. Signal playback start
+ self.signals["start"].emit()
+ self.signals["play"].emit()
+
+ if old_state in (STATE_PLAYING, STATE_PAUSED) and \
+ state in (STATE_IDLE, STATE_NOT_RUNNING):
+ # From playing to finished. Signal end.
+ self.signals["end"].emit()
+
+ if old_state == STATE_PLAYING and state == STATE_PAUSED:
+ self.signals["pause"].emit()
+ self.signals["pause_toggle"].emit()
+
+ if old_state == STATE_PAUSED and state == STATE_PLAYING:
+ self.signals["play"].emit()
+ self.signals["pause_toggle"].emit()
+
+ if state == STATE_NOT_RUNNING == self._player.get_state():
# Player released the video and audio device. Right now we set
# self._player to None to simulate STATE_NOT_RUNNING.
# This needs to be fixed.
self._player = None
+
+ if self.get_state() == STATE_IDLE and not self._pending:
+ # no new mrl to play, release player
+ log.info('release player')
+ return self._player.release()
# Handle pending calls based on the new state. The variable blocked is
# used to avoid calling this function recursive.
@@ -187,13 +210,10 @@
Create a player based on cls.
"""
self._player = cls()
- for sig in self.signals:
- if sig in self._player.signals and \
- not sig in ('start', 'failed', 'end'):
- self._player.signals[sig].connect_weak(self.signals[sig].emit)
- for sig in ('start', 'end', 'release', 'failed'):
- self._player.signals[sig].connect_weak(self._state_change, sig)
-
+ self._player._state_changed.connect_weak(self._state_change)
+ for signal in self._player.signals:
+
self._player.signals[signal].connect_weak(self.signals[signal].emit)
+
@required_states(STATE_NOT_RUNNING, STATE_IDLE)
def _open(self, mrl):
@@ -227,7 +247,7 @@
if not self._player:
self._create_player(cls)
else:
- if not self._player.get_state() in (STATE_IDLE, STATE_NOT_RUNNING):
+ if not self.get_state() in (STATE_IDLE, STATE_NOT_RUNNING,
STATE_SHUTDOWN):
self._player.stop()
if not isinstance(self._player, cls):
self._player.release()
@@ -235,7 +255,7 @@
self._open(mrl)
- @required_states(STATE_IDLE, STATE_PLAYING, STATE_PAUSED)
+ @required_states(STATE_OPEN, STATE_PLAYING, STATE_PAUSED)
def play(self):
if self.get_state() in (STATE_PLAYING, STATE_PAUSED):
# called to toggle play/pause
@@ -261,7 +281,8 @@
"""
Pause playback.
"""
- self._player.pause()
+ if self.get_state() == STATE_PLAYING:
+ self._player.pause()
@required_states(STATE_PLAYING, STATE_PAUSED)
@@ -269,7 +290,8 @@
"""
Resume playback.
"""
- self._player.resume()
+ if self.get_state() == STATE_PAUSED:
+ self._player.resume()
@required_states(STATE_PLAYING, STATE_PAUSED)
@@ -277,21 +299,19 @@
"""
Toggle play / pause.
"""
- state = self.get_state()
- if state == STATE_PLAYING:
+ if self.get_state() == STATE_PLAYING:
self._player.pause()
- if state == STATE_PAUSED:
+ else:
self._player.resume()
- @required_states(STATE_IDLE, STATE_PLAYING, STATE_PAUSED)
+ @required_states(STATE_OPEN, STATE_PLAYING, STATE_PAUSED)
def seek(self, value, type=SEEK_RELATIVE):
"""
Seek. Possible types are SEEK_RELATIVE, SEEK_ABSOLUTE and
SEEK_PERCENTAGE.
"""
- if self.get_state() == STATE_IDLE:
- # FIXME: make it possible to seek between open() and play() and
- # add STATE_IDLE to required_states.
+ if self.get_state() == STATE_OPEN:
+ # FIXME: make it possible to seek between open() and play()
return False
self._player.seek(value, type)
Modified: trunk/popcorn/src/ptypes.py
==============================================================================
--- trunk/popcorn/src/ptypes.py (original)
+++ trunk/popcorn/src/ptypes.py Fri Nov 3 21:26:35 2006
@@ -41,6 +41,7 @@
STATE_NOT_RUNNING = 'STATE_NOT_RUNNING'
STATE_IDLE = 'STATE_IDLE'
STATE_OPENING = 'STATE_OPENING'
+STATE_OPEN = 'STATE_OPEN'
STATE_PLAYING = 'STATE_PLAYING'
STATE_PAUSED = 'STATE_PAUSED'
STATE_SHUTDOWN = 'STATE_SHUTDOWN'
-------------------------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
Freevo-cvslog mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/freevo-cvslog