pyglet 2.1.13__py3-none-any.whl → 3.0.dev1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyglet/__init__.py +67 -61
- pyglet/__init__.pyi +15 -8
- pyglet/app/__init__.py +22 -13
- pyglet/app/async_app.py +212 -0
- pyglet/app/base.py +2 -1
- pyglet/app/{xlib.py → linux.py} +3 -3
- pyglet/config/__init__.py +101 -0
- pyglet/config/gl/__init__.py +30 -0
- pyglet/config/gl/egl.py +120 -0
- pyglet/config/gl/macos.py +262 -0
- pyglet/config/gl/windows.py +267 -0
- pyglet/config/gl/x11.py +142 -0
- pyglet/customtypes.py +43 -2
- pyglet/display/__init__.py +8 -6
- pyglet/display/base.py +3 -63
- pyglet/display/cocoa.py +12 -17
- pyglet/display/emscripten.py +39 -0
- pyglet/display/headless.py +23 -30
- pyglet/display/wayland.py +157 -0
- pyglet/display/win32.py +5 -21
- pyglet/display/xlib.py +19 -27
- pyglet/display/xlib_vidmoderestore.py +2 -2
- pyglet/enums.py +183 -0
- pyglet/event.py +0 -1
- pyglet/experimental/geoshader_sprite.py +15 -13
- pyglet/experimental/hidraw.py +6 -15
- pyglet/experimental/multitexture_sprite.py +31 -19
- pyglet/experimental/particles.py +13 -35
- pyglet/font/__init__.py +251 -85
- pyglet/font/base.py +116 -61
- pyglet/font/dwrite/__init__.py +349 -204
- pyglet/font/dwrite/dwrite_lib.py +27 -5
- pyglet/font/fontconfig.py +14 -6
- pyglet/font/freetype.py +138 -87
- pyglet/font/freetype_lib.py +19 -0
- pyglet/font/group.py +179 -0
- pyglet/font/harfbuzz/__init__.py +3 -3
- pyglet/font/pyodide_js.py +310 -0
- pyglet/font/quartz.py +319 -126
- pyglet/font/ttf.py +45 -3
- pyglet/font/user.py +14 -19
- pyglet/font/win32.py +45 -21
- pyglet/graphics/__init__.py +8 -787
- pyglet/graphics/allocation.py +115 -1
- pyglet/graphics/api/__init__.py +77 -0
- pyglet/graphics/api/base.py +299 -0
- pyglet/graphics/api/gl/__init__.py +58 -0
- pyglet/graphics/api/gl/base.py +24 -0
- pyglet/graphics/{vertexbuffer.py → api/gl/buffer.py} +104 -159
- pyglet/graphics/api/gl/cocoa/context.py +76 -0
- pyglet/graphics/api/gl/context.py +391 -0
- pyglet/graphics/api/gl/default_shaders.py +0 -0
- pyglet/graphics/api/gl/draw.py +627 -0
- pyglet/graphics/api/gl/egl/__init__.py +0 -0
- pyglet/graphics/api/gl/egl/context.py +92 -0
- pyglet/graphics/api/gl/enums.py +76 -0
- pyglet/graphics/api/gl/framebuffer.py +315 -0
- pyglet/graphics/api/gl/gl.py +5463 -0
- pyglet/graphics/api/gl/gl_info.py +188 -0
- pyglet/graphics/api/gl/global_opengl.py +226 -0
- pyglet/{gl → graphics/api/gl}/lib.py +34 -18
- pyglet/graphics/api/gl/shader.py +1476 -0
- pyglet/graphics/api/gl/shapes.py +55 -0
- pyglet/graphics/api/gl/sprite.py +102 -0
- pyglet/graphics/api/gl/state.py +219 -0
- pyglet/graphics/api/gl/text.py +190 -0
- pyglet/graphics/api/gl/texture.py +1526 -0
- pyglet/graphics/{vertexarray.py → api/gl/vertexarray.py} +11 -13
- pyglet/graphics/api/gl/vertexdomain.py +751 -0
- pyglet/graphics/api/gl/win32/__init__.py +0 -0
- pyglet/graphics/api/gl/win32/context.py +108 -0
- pyglet/graphics/api/gl/win32/wgl_info.py +24 -0
- pyglet/graphics/api/gl/xlib/__init__.py +0 -0
- pyglet/graphics/api/gl/xlib/context.py +174 -0
- pyglet/{gl → graphics/api/gl/xlib}/glx_info.py +26 -31
- pyglet/graphics/api/gl1/__init__.py +0 -0
- pyglet/{gl → graphics/api/gl1}/gl_compat.py +3 -2
- pyglet/graphics/api/gl2/__init__.py +0 -0
- pyglet/graphics/api/gl2/buffer.py +320 -0
- pyglet/graphics/api/gl2/draw.py +600 -0
- pyglet/graphics/api/gl2/global_opengl.py +122 -0
- pyglet/graphics/api/gl2/shader.py +200 -0
- pyglet/graphics/api/gl2/shapes.py +51 -0
- pyglet/graphics/api/gl2/sprite.py +79 -0
- pyglet/graphics/api/gl2/text.py +175 -0
- pyglet/graphics/api/gl2/vertexdomain.py +364 -0
- pyglet/graphics/api/webgl/__init__.py +233 -0
- pyglet/graphics/api/webgl/buffer.py +302 -0
- pyglet/graphics/api/webgl/context.py +234 -0
- pyglet/graphics/api/webgl/draw.py +590 -0
- pyglet/graphics/api/webgl/enums.py +76 -0
- pyglet/graphics/api/webgl/framebuffer.py +360 -0
- pyglet/graphics/api/webgl/gl.py +1537 -0
- pyglet/graphics/api/webgl/gl_info.py +130 -0
- pyglet/graphics/api/webgl/shader.py +1346 -0
- pyglet/graphics/api/webgl/shapes.py +92 -0
- pyglet/graphics/api/webgl/sprite.py +102 -0
- pyglet/graphics/api/webgl/state.py +227 -0
- pyglet/graphics/api/webgl/text.py +187 -0
- pyglet/graphics/api/webgl/texture.py +1227 -0
- pyglet/graphics/api/webgl/vertexarray.py +54 -0
- pyglet/graphics/api/webgl/vertexdomain.py +616 -0
- pyglet/graphics/api/webgl/webgl_js.pyi +307 -0
- pyglet/{image → graphics}/atlas.py +33 -32
- pyglet/graphics/base.py +10 -0
- pyglet/graphics/buffer.py +245 -0
- pyglet/graphics/draw.py +578 -0
- pyglet/graphics/framebuffer.py +26 -0
- pyglet/graphics/instance.py +178 -69
- pyglet/graphics/shader.py +267 -1553
- pyglet/graphics/state.py +83 -0
- pyglet/graphics/texture.py +703 -0
- pyglet/graphics/vertexdomain.py +695 -538
- pyglet/gui/ninepatch.py +10 -10
- pyglet/gui/widgets.py +120 -10
- pyglet/image/__init__.py +20 -1973
- pyglet/image/animation.py +12 -12
- pyglet/image/base.py +730 -0
- pyglet/image/codecs/__init__.py +9 -0
- pyglet/image/codecs/bmp.py +53 -30
- pyglet/image/codecs/dds.py +53 -31
- pyglet/image/codecs/gdiplus.py +38 -14
- pyglet/image/codecs/gdkpixbuf2.py +0 -2
- pyglet/image/codecs/js_image.py +99 -0
- pyglet/image/codecs/ktx2.py +161 -0
- pyglet/image/codecs/pil.py +1 -1
- pyglet/image/codecs/png.py +1 -1
- pyglet/image/codecs/wic.py +11 -2
- pyglet/info.py +26 -24
- pyglet/input/__init__.py +8 -0
- pyglet/input/base.py +163 -105
- pyglet/input/controller.py +13 -19
- pyglet/input/controller_db.py +39 -24
- pyglet/input/emscripten/__init__.py +18 -0
- pyglet/input/emscripten/gamepad_js.py +397 -0
- pyglet/input/linux/__init__.py +11 -5
- pyglet/input/linux/evdev.py +10 -11
- pyglet/input/linux/x11_xinput.py +2 -2
- pyglet/input/linux/x11_xinput_tablet.py +1 -1
- pyglet/input/macos/__init__.py +7 -2
- pyglet/input/macos/darwin_gc.py +559 -0
- pyglet/input/win32/__init__.py +1 -1
- pyglet/input/win32/directinput.py +34 -29
- pyglet/input/win32/xinput.py +11 -61
- pyglet/lib.py +3 -3
- pyglet/libs/__init__.py +1 -1
- pyglet/{gl → libs/darwin}/agl.py +1 -1
- pyglet/libs/darwin/cocoapy/__init__.py +2 -2
- pyglet/libs/darwin/cocoapy/cocoahelpers.py +181 -0
- pyglet/libs/darwin/cocoapy/cocoalibs.py +31 -0
- pyglet/libs/darwin/cocoapy/cocoatypes.py +27 -0
- pyglet/libs/darwin/cocoapy/runtime.py +81 -45
- pyglet/libs/darwin/coreaudio.py +4 -4
- pyglet/{gl → libs/darwin}/lib_agl.py +9 -8
- pyglet/libs/darwin/quartzkey.py +1 -3
- pyglet/libs/egl/__init__.py +2 -0
- pyglet/libs/egl/egl_lib.py +576 -0
- pyglet/libs/egl/eglext.py +51 -5
- pyglet/libs/linux/__init__.py +0 -0
- pyglet/libs/linux/egl/__init__.py +0 -0
- pyglet/libs/linux/egl/eglext.py +22 -0
- pyglet/libs/linux/glx/__init__.py +0 -0
- pyglet/{gl → libs/linux/glx}/glx.py +13 -14
- pyglet/{gl → libs/linux/glx}/glxext_arb.py +408 -192
- pyglet/{gl → libs/linux/glx}/glxext_mesa.py +1 -1
- pyglet/{gl → libs/linux/glx}/glxext_nv.py +345 -164
- pyglet/{gl → libs/linux/glx}/lib_glx.py +3 -2
- pyglet/libs/linux/wayland/__init__.py +0 -0
- pyglet/libs/linux/wayland/client.py +1068 -0
- pyglet/libs/linux/wayland/lib_wayland.py +207 -0
- pyglet/libs/linux/wayland/wayland_egl.py +38 -0
- pyglet/libs/{wayland → linux/wayland}/xkbcommon.py +26 -0
- pyglet/libs/{x11 → linux/x11}/xf86vmode.py +4 -4
- pyglet/libs/{x11 → linux/x11}/xinerama.py +2 -2
- pyglet/libs/{x11 → linux/x11}/xinput.py +10 -10
- pyglet/libs/linux/x11/xrandr.py +0 -0
- pyglet/libs/{x11 → linux/x11}/xrender.py +1 -1
- pyglet/libs/shared/__init__.py +0 -0
- pyglet/libs/shared/spirv/__init__.py +0 -0
- pyglet/libs/shared/spirv/lib_shaderc.py +85 -0
- pyglet/libs/shared/spirv/lib_spirv_cross.py +126 -0
- pyglet/libs/win32/__init__.py +28 -8
- pyglet/libs/win32/constants.py +59 -48
- pyglet/libs/win32/context_managers.py +20 -3
- pyglet/libs/win32/dinput.py +105 -88
- pyglet/{gl → libs/win32}/lib_wgl.py +52 -26
- pyglet/libs/win32/types.py +58 -23
- pyglet/{gl → libs/win32}/wgl.py +32 -25
- pyglet/{gl → libs/win32}/wglext_arb.py +364 -2
- pyglet/media/__init__.py +9 -10
- pyglet/media/codecs/__init__.py +12 -1
- pyglet/media/codecs/base.py +99 -96
- pyglet/media/codecs/ffmpeg.py +2 -2
- pyglet/media/codecs/ffmpeg_lib/libavformat.py +3 -8
- pyglet/media/codecs/webaudio_pyodide.py +111 -0
- pyglet/media/drivers/__init__.py +9 -4
- pyglet/media/drivers/base.py +4 -4
- pyglet/media/drivers/openal/__init__.py +1 -1
- pyglet/media/drivers/openal/adaptation.py +3 -3
- pyglet/media/drivers/pulse/__init__.py +1 -1
- pyglet/media/drivers/pulse/adaptation.py +3 -3
- pyglet/media/drivers/pyodide_js/__init__.py +8 -0
- pyglet/media/drivers/pyodide_js/adaptation.py +288 -0
- pyglet/media/drivers/xaudio2/adaptation.py +3 -3
- pyglet/media/player.py +276 -193
- pyglet/media/player_worker_thread.py +1 -1
- pyglet/model/__init__.py +39 -29
- pyglet/model/codecs/base.py +4 -4
- pyglet/model/codecs/gltf.py +3 -3
- pyglet/model/codecs/obj.py +71 -43
- pyglet/resource.py +129 -78
- pyglet/shapes.py +154 -194
- pyglet/sprite.py +47 -164
- pyglet/text/__init__.py +44 -54
- pyglet/text/caret.py +12 -7
- pyglet/text/document.py +19 -17
- pyglet/text/formats/html.py +2 -2
- pyglet/text/formats/structured.py +10 -40
- pyglet/text/layout/__init__.py +20 -13
- pyglet/text/layout/base.py +176 -287
- pyglet/text/layout/incremental.py +9 -10
- pyglet/text/layout/scrolling.py +7 -95
- pyglet/window/__init__.py +183 -172
- pyglet/window/cocoa/__init__.py +62 -51
- pyglet/window/cocoa/pyglet_delegate.py +2 -25
- pyglet/window/cocoa/pyglet_view.py +9 -8
- pyglet/window/dialog/__init__.py +184 -0
- pyglet/window/dialog/base.py +99 -0
- pyglet/window/dialog/darwin.py +121 -0
- pyglet/window/dialog/linux.py +72 -0
- pyglet/window/dialog/windows.py +194 -0
- pyglet/window/emscripten/__init__.py +779 -0
- pyglet/window/headless/__init__.py +44 -28
- pyglet/window/key.py +2 -0
- pyglet/window/mouse.py +2 -2
- pyglet/window/wayland/__init__.py +377 -0
- pyglet/window/win32/__init__.py +101 -46
- pyglet/window/xlib/__init__.py +104 -66
- {pyglet-2.1.13.dist-info → pyglet-3.0.dev1.dist-info}/METADATA +2 -3
- pyglet-3.0.dev1.dist-info/RECORD +322 -0
- {pyglet-2.1.13.dist-info → pyglet-3.0.dev1.dist-info}/WHEEL +1 -1
- pyglet/gl/__init__.py +0 -208
- pyglet/gl/base.py +0 -499
- pyglet/gl/cocoa.py +0 -309
- pyglet/gl/gl.py +0 -4625
- pyglet/gl/gl.pyi +0 -2320
- pyglet/gl/gl_compat.pyi +0 -3097
- pyglet/gl/gl_info.py +0 -190
- pyglet/gl/headless.py +0 -166
- pyglet/gl/wgl_info.py +0 -36
- pyglet/gl/wglext_nv.py +0 -1096
- pyglet/gl/win32.py +0 -268
- pyglet/gl/xlib.py +0 -295
- pyglet/image/buffer.py +0 -274
- pyglet/image/codecs/s3tc.py +0 -354
- pyglet/libs/x11/xrandr.py +0 -166
- pyglet-2.1.13.dist-info/RECORD +0 -234
- /pyglet/{libs/wayland → graphics/api/gl/cocoa}/__init__.py +0 -0
- /pyglet/libs/{egl → linux/egl}/egl.py +0 -0
- /pyglet/libs/{egl → linux/egl}/lib.py +0 -0
- /pyglet/libs/{ioctl.py → linux/ioctl.py} +0 -0
- /pyglet/libs/{wayland → linux/wayland}/gbm.py +0 -0
- /pyglet/libs/{x11 → linux/x11}/__init__.py +0 -0
- /pyglet/libs/{x11 → linux/x11}/cursorfont.py +0 -0
- /pyglet/libs/{x11 → linux/x11}/xlib.py +0 -0
- /pyglet/libs/{x11 → linux/x11}/xsync.py +0 -0
- {pyglet-2.1.13.dist-info/licenses → pyglet-3.0.dev1.dist-info}/LICENSE +0 -0
pyglet/media/player.py
CHANGED
|
@@ -3,20 +3,18 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import time
|
|
5
5
|
from collections import deque
|
|
6
|
-
|
|
7
|
-
from typing import TYPE_CHECKING, Iterable, Generator
|
|
6
|
+
from typing import TYPE_CHECKING, Generator, Iterable
|
|
8
7
|
|
|
9
8
|
import pyglet
|
|
10
|
-
|
|
11
|
-
from pyglet.gl import GL_TEXTURE_2D
|
|
12
|
-
from pyglet.media import buffered_logger as bl
|
|
13
|
-
from pyglet.media.drivers import get_audio_driver
|
|
14
9
|
from pyglet.media.codecs.base import Source, SourceGroup
|
|
10
|
+
from pyglet.media.drivers import get_audio_driver
|
|
11
|
+
from pyglet.media.exceptions import MediaException
|
|
12
|
+
from pyglet.util import debug_print
|
|
15
13
|
|
|
16
|
-
_debug =
|
|
14
|
+
_debug = debug_print('debug_media')
|
|
17
15
|
|
|
18
16
|
if TYPE_CHECKING:
|
|
19
|
-
from pyglet.
|
|
17
|
+
from pyglet.graphics import Texture
|
|
20
18
|
|
|
21
19
|
|
|
22
20
|
class PlaybackTimer:
|
|
@@ -54,8 +52,7 @@ class PlaybackTimer:
|
|
|
54
52
|
return time.perf_counter() - self._started_at + self._elapsed
|
|
55
53
|
|
|
56
54
|
def set_time(self, value: float) -> None:
|
|
57
|
-
"""
|
|
58
|
-
Manually set the elapsed time.
|
|
55
|
+
"""Manually set the elapsed time.
|
|
59
56
|
|
|
60
57
|
Args:
|
|
61
58
|
value (float): the new elapsed time value
|
|
@@ -94,21 +91,21 @@ class _PlayerProperty:
|
|
|
94
91
|
getattr(obj._audio_player, self.setter_name)(value)
|
|
95
92
|
|
|
96
93
|
|
|
97
|
-
class
|
|
98
|
-
"""High-level sound
|
|
94
|
+
class AudioPlayer(pyglet.event.EventDispatcher):
|
|
95
|
+
"""High-level sound player."""
|
|
99
96
|
|
|
100
97
|
# Specialisation attributes, preserved between audio players
|
|
101
98
|
_volume = 1.0
|
|
102
99
|
_min_distance = 1.0
|
|
103
|
-
_max_distance = 100000000.
|
|
100
|
+
_max_distance = 100000000.0
|
|
104
101
|
|
|
105
102
|
_position = (0, 0, 0)
|
|
106
103
|
_pitch = 1.0
|
|
107
104
|
|
|
108
105
|
_cone_orientation = (0, 0, 1)
|
|
109
|
-
_cone_inner_angle = 360.
|
|
110
|
-
_cone_outer_angle = 360.
|
|
111
|
-
_cone_outer_gain = 1.
|
|
106
|
+
_cone_inner_angle = 360.0
|
|
107
|
+
_cone_outer_angle = 360.0
|
|
108
|
+
_cone_outer_gain = 1.0
|
|
112
109
|
|
|
113
110
|
def __init__(self) -> None:
|
|
114
111
|
"""Initialize the Player with a MasterClock."""
|
|
@@ -116,9 +113,6 @@ class Player(pyglet.event.EventDispatcher):
|
|
|
116
113
|
self._playlists = deque()
|
|
117
114
|
self._audio_player = None
|
|
118
115
|
|
|
119
|
-
self._context = pyglet.gl.current_context
|
|
120
|
-
self._texture = None
|
|
121
|
-
|
|
122
116
|
# Desired play state (not an indication of actual state).
|
|
123
117
|
self._playing = False
|
|
124
118
|
|
|
@@ -149,8 +143,7 @@ class Player(pyglet.event.EventDispatcher):
|
|
|
149
143
|
try:
|
|
150
144
|
source = iter(source)
|
|
151
145
|
except TypeError:
|
|
152
|
-
raise TypeError("source must be either a Source or an iterable."
|
|
153
|
-
" Received type {0}".format(type(source)))
|
|
146
|
+
raise TypeError(f"source must be either a Source or an iterable. Received type {type(source)}")
|
|
154
147
|
self._playlists.append(source)
|
|
155
148
|
|
|
156
149
|
if self.source is None:
|
|
@@ -164,6 +157,41 @@ class Player(pyglet.event.EventDispatcher):
|
|
|
164
157
|
else:
|
|
165
158
|
self._source = new_source.get_queue_source()
|
|
166
159
|
|
|
160
|
+
def _check_resource_reuse(self, old_source: Source) -> None:
|
|
161
|
+
"""Determine if a resource can be re-used based on the old source."""
|
|
162
|
+
if self._audio_player is not None:
|
|
163
|
+
# If current source format properties match the old source, then the resource can be re-used.
|
|
164
|
+
if self._source.audio_format == old_source.audio_format:
|
|
165
|
+
self._audio_player.set_source(self._source)
|
|
166
|
+
else:
|
|
167
|
+
# Delete the resource so a matching resource can be created.
|
|
168
|
+
self._audio_player.delete()
|
|
169
|
+
self._audio_player = None
|
|
170
|
+
|
|
171
|
+
def _create_player_resources(self, starting: bool) -> None:
|
|
172
|
+
"""Creates driver resources for the particular format."""
|
|
173
|
+
if self._source.audio_format is not None:
|
|
174
|
+
if was_created := self._audio_player is None:
|
|
175
|
+
self._create_audio_player()
|
|
176
|
+
if self._audio_player is not None and (was_created or starting):
|
|
177
|
+
self._audio_player.prefill_audio()
|
|
178
|
+
|
|
179
|
+
def _start_player_resources(self) -> None:
|
|
180
|
+
"""Start any driver related resources required for playback."""
|
|
181
|
+
if self._audio_player is not None:
|
|
182
|
+
self._audio_player.play()
|
|
183
|
+
|
|
184
|
+
def _stop_player_resources(self) -> None:
|
|
185
|
+
"""Stop any driver related resources required for playback."""
|
|
186
|
+
if self._audio_player is not None:
|
|
187
|
+
self._audio_player.stop()
|
|
188
|
+
|
|
189
|
+
def _seek_player_resources(self) -> None:
|
|
190
|
+
if self._audio_player is not None:
|
|
191
|
+
# XXX: According to docstring in AbstractAudioPlayer this cannot
|
|
192
|
+
# be called when the player is not stopped
|
|
193
|
+
self._audio_player.clear()
|
|
194
|
+
|
|
167
195
|
def _set_playing(self, playing: bool) -> None:
|
|
168
196
|
# stopping = self._playing and not playing
|
|
169
197
|
starting = not self._playing and playing
|
|
@@ -172,44 +200,22 @@ class Player(pyglet.event.EventDispatcher):
|
|
|
172
200
|
source = self.source
|
|
173
201
|
|
|
174
202
|
if playing and source:
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
self._create_audio_player()
|
|
178
|
-
if self._audio_player is not None and (was_created or starting):
|
|
179
|
-
self._audio_player.prefill_audio()
|
|
180
|
-
|
|
181
|
-
if bl.logger is not None:
|
|
182
|
-
bl.logger.init_wall_time()
|
|
183
|
-
bl.logger.log("p.P._sp", 0.0)
|
|
184
|
-
|
|
185
|
-
if source.video_format is not None:
|
|
186
|
-
if self._texture is None:
|
|
187
|
-
self._create_texture()
|
|
188
|
-
|
|
189
|
-
if self._audio_player is not None:
|
|
190
|
-
self._audio_player.play()
|
|
191
|
-
if source.video_format is not None:
|
|
192
|
-
pyglet.clock.schedule_once(self.update_texture, 0)
|
|
193
|
-
# For audio synchronization tests, the following will
|
|
194
|
-
# add a delay to de-synchronize the audio.
|
|
195
|
-
# Negative number means audio runs ahead.
|
|
196
|
-
# self._mclock._systime += -0.3
|
|
203
|
+
self._create_player_resources(starting)
|
|
204
|
+
self._start_player_resources()
|
|
197
205
|
self._timer.start()
|
|
206
|
+
|
|
207
|
+
# If the driver is silent, it will not create any players, dispatch an end event.
|
|
198
208
|
if self._audio_player is None and source.video_format is None:
|
|
199
209
|
pyglet.clock.schedule_once(lambda dt: self.dispatch_event("on_eos"), source.duration)
|
|
200
|
-
|
|
201
210
|
else:
|
|
202
|
-
|
|
203
|
-
self._audio_player.stop()
|
|
204
|
-
|
|
205
|
-
pyglet.clock.unschedule(self.update_texture)
|
|
211
|
+
self._stop_player_resources()
|
|
206
212
|
self._timer.pause()
|
|
207
213
|
|
|
208
214
|
@property
|
|
209
215
|
def playing(self) -> bool:
|
|
210
216
|
"""The current playing state.
|
|
211
217
|
|
|
212
|
-
The *playing* property is irrespective of whether
|
|
218
|
+
The *playing* property is irrespective of whether there is
|
|
213
219
|
actually a source to play. If *playing* is ``True`` and a source is
|
|
214
220
|
queued, it will begin to play immediately. If *playing* is ``False``,
|
|
215
221
|
it is implied that the player is paused. There is no other possible
|
|
@@ -241,8 +247,6 @@ class Player(pyglet.event.EventDispatcher):
|
|
|
241
247
|
if self._audio_player is not None:
|
|
242
248
|
self._audio_player.delete()
|
|
243
249
|
self._audio_player = None
|
|
244
|
-
if self._texture is not None:
|
|
245
|
-
self._texture = None
|
|
246
250
|
|
|
247
251
|
def next_source(self) -> None:
|
|
248
252
|
"""Move immediately to the next source in the current playlist.
|
|
@@ -285,15 +289,7 @@ class Player(pyglet.event.EventDispatcher):
|
|
|
285
289
|
old_source = self._source
|
|
286
290
|
self._set_source(new_source)
|
|
287
291
|
|
|
288
|
-
|
|
289
|
-
if self._source.audio_format == old_source.audio_format:
|
|
290
|
-
self._audio_player.set_source(self._source)
|
|
291
|
-
else:
|
|
292
|
-
self._audio_player.delete()
|
|
293
|
-
self._audio_player = None
|
|
294
|
-
if self._source.video_format != old_source.video_format:
|
|
295
|
-
self._texture = None
|
|
296
|
-
pyglet.clock.unschedule(self.update_texture)
|
|
292
|
+
self._check_resource_reuse(old_source)
|
|
297
293
|
|
|
298
294
|
del old_source
|
|
299
295
|
|
|
@@ -312,9 +308,6 @@ class Player(pyglet.event.EventDispatcher):
|
|
|
312
308
|
if self.source is None:
|
|
313
309
|
return
|
|
314
310
|
|
|
315
|
-
if bl.logger is not None:
|
|
316
|
-
bl.logger.log("p.P.sk", timestamp)
|
|
317
|
-
|
|
318
311
|
timestamp = max(timestamp, 0.0)
|
|
319
312
|
if self._source.duration is not None:
|
|
320
313
|
# TODO: If the duration is reported as None and the source clamps anyways,
|
|
@@ -326,13 +319,8 @@ class Player(pyglet.event.EventDispatcher):
|
|
|
326
319
|
self._source.seek(timestamp)
|
|
327
320
|
self.last_seek_time = timestamp
|
|
328
321
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
# be called when the player is not stopped
|
|
332
|
-
self._audio_player.clear()
|
|
333
|
-
if self.source.video_format is not None:
|
|
334
|
-
self.update_texture()
|
|
335
|
-
pyglet.clock.unschedule(self.update_texture)
|
|
322
|
+
self._seek_player_resources()
|
|
323
|
+
|
|
336
324
|
self._set_playing(playing)
|
|
337
325
|
|
|
338
326
|
def _create_audio_player(self) -> None:
|
|
@@ -348,9 +336,17 @@ class Player(pyglet.event.EventDispatcher):
|
|
|
348
336
|
self._audio_player = audio_driver.create_audio_player(source, self)
|
|
349
337
|
|
|
350
338
|
# Set the audio player attributes
|
|
351
|
-
for attr in (
|
|
352
|
-
|
|
353
|
-
|
|
339
|
+
for attr in (
|
|
340
|
+
'volume',
|
|
341
|
+
'min_distance',
|
|
342
|
+
'max_distance',
|
|
343
|
+
'position',
|
|
344
|
+
'pitch',
|
|
345
|
+
'cone_orientation',
|
|
346
|
+
'cone_inner_angle',
|
|
347
|
+
'cone_outer_angle',
|
|
348
|
+
'cone_outer_gain',
|
|
349
|
+
):
|
|
354
350
|
value = getattr(self, attr)
|
|
355
351
|
setattr(self, attr, value)
|
|
356
352
|
|
|
@@ -370,107 +366,6 @@ class Player(pyglet.event.EventDispatcher):
|
|
|
370
366
|
"""
|
|
371
367
|
return self._timer.get_time()
|
|
372
368
|
|
|
373
|
-
def _create_texture(self) -> None:
|
|
374
|
-
video_format = self.source.video_format
|
|
375
|
-
self._texture = pyglet.image.Texture.create(video_format.width, video_format.height, GL_TEXTURE_2D)
|
|
376
|
-
self._texture = self._texture.get_transform(flip_y=True)
|
|
377
|
-
# After flipping the texture along the y axis, the anchor_y is set
|
|
378
|
-
# to the top of the image. We want to keep it at the bottom.
|
|
379
|
-
self._texture.anchor_y = 0
|
|
380
|
-
return self._texture
|
|
381
|
-
|
|
382
|
-
@property
|
|
383
|
-
def texture(self) -> Texture | None:
|
|
384
|
-
"""Get the texture for the current video frame, if any.
|
|
385
|
-
|
|
386
|
-
You should query this property every time you display a frame of video,
|
|
387
|
-
as multiple textures might be used. This property will be ``None`` if
|
|
388
|
-
the current Source does not contain video.
|
|
389
|
-
"""
|
|
390
|
-
return self._texture
|
|
391
|
-
|
|
392
|
-
def seek_next_frame(self) -> None:
|
|
393
|
-
"""Step forwards one video frame in the current source."""
|
|
394
|
-
time = self.source.get_next_video_timestamp()
|
|
395
|
-
if time is None:
|
|
396
|
-
return
|
|
397
|
-
self.seek(time)
|
|
398
|
-
|
|
399
|
-
def update_texture(self, dt: float | None = None) -> None:
|
|
400
|
-
"""Manually update the texture from the current source.
|
|
401
|
-
|
|
402
|
-
This happens automatically, so you shouldn't need to call this method.
|
|
403
|
-
|
|
404
|
-
Args:
|
|
405
|
-
dt:
|
|
406
|
-
The time elapsed since the last call to ``update_texture``.
|
|
407
|
-
"""
|
|
408
|
-
# self.pr.disable()
|
|
409
|
-
# if dt > 0.05:
|
|
410
|
-
# print("update_texture dt:", dt)
|
|
411
|
-
# import pstats
|
|
412
|
-
# ps = pstats.Stats(self.pr).sort_stats("cumulative")
|
|
413
|
-
# ps.print_stats()
|
|
414
|
-
source = self.source
|
|
415
|
-
time = self.time
|
|
416
|
-
if bl.logger is not None:
|
|
417
|
-
bl.logger.log(
|
|
418
|
-
"p.P.ut.1.0", dt, time,
|
|
419
|
-
self._audio_player.get_time() if self._audio_player else 0,
|
|
420
|
-
bl.logger.rebased_wall_time()
|
|
421
|
-
)
|
|
422
|
-
|
|
423
|
-
frame_rate = source.video_format.frame_rate
|
|
424
|
-
frame_duration = 1 / frame_rate
|
|
425
|
-
ts = source.get_next_video_timestamp()
|
|
426
|
-
# Allow up to frame_duration difference
|
|
427
|
-
while ts is not None and ts + frame_duration < time:
|
|
428
|
-
source.get_next_video_frame() # Discard frame
|
|
429
|
-
if bl.logger is not None:
|
|
430
|
-
bl.logger.log("p.P.ut.1.5", ts)
|
|
431
|
-
ts = source.get_next_video_timestamp()
|
|
432
|
-
|
|
433
|
-
if bl.logger is not None:
|
|
434
|
-
bl.logger.log("p.P.ut.1.6", ts)
|
|
435
|
-
|
|
436
|
-
if ts is None:
|
|
437
|
-
# No more video frames to show. End of video stream.
|
|
438
|
-
if bl.logger is not None:
|
|
439
|
-
bl.logger.log("p.P.ut.1.7", frame_duration)
|
|
440
|
-
|
|
441
|
-
pyglet.clock.schedule_once(self._video_finished, 0)
|
|
442
|
-
return
|
|
443
|
-
elif ts > time:
|
|
444
|
-
# update_texture called too early (probably manually!)
|
|
445
|
-
pyglet.clock.schedule_once(self.update_texture, ts - time)
|
|
446
|
-
return
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
image = source.get_next_video_frame()
|
|
450
|
-
if image is not None:
|
|
451
|
-
with self._context:
|
|
452
|
-
if self._texture is None:
|
|
453
|
-
self._create_texture()
|
|
454
|
-
self._texture.blit_into(image, 0, 0, 0)
|
|
455
|
-
elif bl.logger is not None:
|
|
456
|
-
bl.logger.log("p.P.ut.1.8")
|
|
457
|
-
|
|
458
|
-
ts = source.get_next_video_timestamp()
|
|
459
|
-
if ts is None:
|
|
460
|
-
delay = frame_duration
|
|
461
|
-
else:
|
|
462
|
-
delay = ts - time
|
|
463
|
-
|
|
464
|
-
delay = max(0.0, delay)
|
|
465
|
-
if bl.logger is not None:
|
|
466
|
-
bl.logger.log("p.P.ut.1.9", delay, ts)
|
|
467
|
-
pyglet.clock.schedule_once(self.update_texture, delay)
|
|
468
|
-
# self.pr.enable()
|
|
469
|
-
|
|
470
|
-
def _video_finished(self, _dt: float) -> None:
|
|
471
|
-
if self._audio_player is None:
|
|
472
|
-
self.dispatch_event("on_eos")
|
|
473
|
-
|
|
474
369
|
volume = _PlayerProperty('volume', doc="""
|
|
475
370
|
The volume level of sound playback.
|
|
476
371
|
|
|
@@ -546,8 +441,7 @@ class Player(pyglet.event.EventDispatcher):
|
|
|
546
441
|
|
|
547
442
|
def on_player_eos(self):
|
|
548
443
|
"""The player ran out of sources. The playlist is empty."""
|
|
549
|
-
|
|
550
|
-
print('Player.on_player_eos')
|
|
444
|
+
assert _debug('Player.on_player_eos')
|
|
551
445
|
|
|
552
446
|
def on_eos(self):
|
|
553
447
|
"""The current source ran out of data.
|
|
@@ -558,11 +452,7 @@ class Player(pyglet.event.EventDispatcher):
|
|
|
558
452
|
will start to play again until :meth:`next_source` is called or
|
|
559
453
|
:attr:`.loop` is set to ``False``.
|
|
560
454
|
"""
|
|
561
|
-
|
|
562
|
-
print('Player.on_eos')
|
|
563
|
-
if bl.logger is not None:
|
|
564
|
-
bl.logger.log("p.P.oe")
|
|
565
|
-
bl.logger.close()
|
|
455
|
+
assert _debug('Player.on_eos')
|
|
566
456
|
|
|
567
457
|
if self.loop:
|
|
568
458
|
was_playing = self._playing
|
|
@@ -587,7 +477,7 @@ class Player(pyglet.event.EventDispatcher):
|
|
|
587
477
|
def on_driver_reset(self):
|
|
588
478
|
"""The audio driver has been reset.
|
|
589
479
|
|
|
590
|
-
By default this will kill the current audio player, create a new one,
|
|
480
|
+
By default, this will kill the current audio player, create a new one,
|
|
591
481
|
and requeue the buffers. Any buffers that may have been queued in a
|
|
592
482
|
player will be resubmitted. It will continue from the last buffers
|
|
593
483
|
submitted, not played, and may cause sync issues if using video.
|
|
@@ -598,8 +488,17 @@ class Player(pyglet.event.EventDispatcher):
|
|
|
598
488
|
self._audio_player.on_driver_reset()
|
|
599
489
|
|
|
600
490
|
# Voice has been changed, will need to reset all options on the voice.
|
|
601
|
-
for attr in (
|
|
602
|
-
|
|
491
|
+
for attr in (
|
|
492
|
+
'volume',
|
|
493
|
+
'min_distance',
|
|
494
|
+
'max_distance',
|
|
495
|
+
'position',
|
|
496
|
+
'pitch',
|
|
497
|
+
'cone_orientation',
|
|
498
|
+
'cone_inner_angle',
|
|
499
|
+
'cone_outer_angle',
|
|
500
|
+
'cone_outer_gain',
|
|
501
|
+
):
|
|
603
502
|
value = getattr(self, attr)
|
|
604
503
|
setattr(self, attr, value)
|
|
605
504
|
|
|
@@ -607,16 +506,196 @@ class Player(pyglet.event.EventDispatcher):
|
|
|
607
506
|
self._audio_player.play()
|
|
608
507
|
|
|
609
508
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
509
|
+
AudioPlayer.register_event_type('on_eos')
|
|
510
|
+
AudioPlayer.register_event_type('on_player_eos')
|
|
511
|
+
AudioPlayer.register_event_type('on_player_next_source')
|
|
512
|
+
AudioPlayer.register_event_type('on_driver_reset')
|
|
614
513
|
|
|
615
514
|
|
|
616
515
|
def _one_item_playlist(source: Source) -> Generator:
|
|
617
516
|
yield source
|
|
618
517
|
|
|
619
518
|
|
|
519
|
+
class VideoPlayer(AudioPlayer):
|
|
520
|
+
"""High-level sound and video player."""
|
|
521
|
+
_sprite: pyglet.sprite.Sprite | None
|
|
522
|
+
|
|
523
|
+
def __init__(self, batch: pyglet.graphics.Batch | None = None) -> None:
|
|
524
|
+
super().__init__()
|
|
525
|
+
self._texture = None
|
|
526
|
+
self._sprite = None
|
|
527
|
+
self._position = (0, 0)
|
|
528
|
+
self._width = 0
|
|
529
|
+
self._height = 0
|
|
530
|
+
self.batch = batch or pyglet.graphics.get_default_batch()
|
|
531
|
+
self._context = batch._context # noqa: SLF001
|
|
532
|
+
|
|
533
|
+
self._check_ffmpeg_availability()
|
|
534
|
+
|
|
535
|
+
@staticmethod
|
|
536
|
+
def _check_ffmpeg_availability() -> None:
|
|
537
|
+
# Check if a source is FFmpeg instead?
|
|
538
|
+
from pyglet.media import have_ffmpeg
|
|
539
|
+
if not have_ffmpeg():
|
|
540
|
+
raise MediaException("VideoPlayer requires FFmpeg to be installed.")
|
|
541
|
+
|
|
542
|
+
@property
|
|
543
|
+
def position(self) -> tuple[int, int]:
|
|
544
|
+
"""Get the position of the video player."""
|
|
545
|
+
return self._position
|
|
546
|
+
|
|
547
|
+
@position.setter
|
|
548
|
+
def position(self, value: tuple[int, int]) -> None:
|
|
549
|
+
self._position = value
|
|
550
|
+
if self._sprite:
|
|
551
|
+
self._sprite.position = (*value, 0)
|
|
552
|
+
|
|
553
|
+
@property
|
|
554
|
+
def width(self) -> int:
|
|
555
|
+
"""Get the width of the video player."""
|
|
556
|
+
return self._width
|
|
557
|
+
|
|
558
|
+
@width.setter
|
|
559
|
+
def width(self, width: int) -> None:
|
|
560
|
+
self._width = width
|
|
561
|
+
if self._sprite:
|
|
562
|
+
self._sprite.width = width
|
|
563
|
+
|
|
564
|
+
@property
|
|
565
|
+
def height(self) -> int:
|
|
566
|
+
"""Get the height of the video player."""
|
|
567
|
+
return self._height
|
|
568
|
+
|
|
569
|
+
@height.setter
|
|
570
|
+
def height(self, height: int) -> None:
|
|
571
|
+
self._height = height
|
|
572
|
+
if self._sprite:
|
|
573
|
+
self._sprite.height = height
|
|
574
|
+
|
|
575
|
+
def _seek_player_resources(self) -> None:
|
|
576
|
+
super()._seek_player_resources()
|
|
577
|
+
if self.source.video_format is not None:
|
|
578
|
+
self.update_texture()
|
|
579
|
+
pyglet.clock.unschedule(self.update_texture)
|
|
580
|
+
|
|
581
|
+
def _check_resource_reuse(self, old_source: Source) -> None:
|
|
582
|
+
super()._check_resource_reuse(old_source)
|
|
583
|
+
if self._source.video_format != old_source.video_format:
|
|
584
|
+
pyglet.clock.unschedule(self.update_texture)
|
|
585
|
+
self._texture = None
|
|
586
|
+
self._sprite = None
|
|
587
|
+
|
|
588
|
+
def _create_player_resources(self, starting: bool) -> None:
|
|
589
|
+
super()._create_player_resources(starting)
|
|
590
|
+
if self._source.video_format is not None:
|
|
591
|
+
if self._texture is None:
|
|
592
|
+
with self._context:
|
|
593
|
+
self._create_texture()
|
|
594
|
+
self._create_sprite()
|
|
595
|
+
|
|
596
|
+
def _create_texture(self) -> pyglet.graphics.Texture:
|
|
597
|
+
video_format = self.source.video_format
|
|
598
|
+
self._texture = pyglet.graphics.Texture.create(video_format.width, video_format.height)
|
|
599
|
+
|
|
600
|
+
# Flip coordinates so output is correct orientation.
|
|
601
|
+
self._texture = self._texture.get_transform(flip_y=True)
|
|
602
|
+
# After flipping, the anchor_y is set to the top of the image. We want to keep it at the bottom.
|
|
603
|
+
self._texture.anchor_y = 0
|
|
604
|
+
return self._texture
|
|
605
|
+
|
|
606
|
+
def _create_sprite(self) -> None:
|
|
607
|
+
if not self._sprite:
|
|
608
|
+
self._sprite = pyglet.sprite.Sprite(self._texture, x=self._position[0], y=self._position[1], batch=self.batch)
|
|
609
|
+
|
|
610
|
+
def _start_player_resources(self) -> None:
|
|
611
|
+
super()._start_player_resources()
|
|
612
|
+
if self._source.video_format is not None:
|
|
613
|
+
pyglet.clock.schedule_once(self.update_texture, 0)
|
|
614
|
+
|
|
615
|
+
def _stop_player_resources(self):
|
|
616
|
+
super()._stop_player_resources()
|
|
617
|
+
pyglet.clock.unschedule(self.update_texture)
|
|
618
|
+
|
|
619
|
+
def draw(self):
|
|
620
|
+
assert pyglet.graphics.api.core.current_context == self._context, "Drawing in the wrong context."
|
|
621
|
+
if self._source:
|
|
622
|
+
self.batch.draw()
|
|
623
|
+
|
|
624
|
+
def update_texture(self, dt: float | None = None) -> None:
|
|
625
|
+
"""Manually update the texture from the current source.
|
|
626
|
+
|
|
627
|
+
This happens automatically, so you shouldn't need to call this method.
|
|
628
|
+
|
|
629
|
+
Args:
|
|
630
|
+
dt:
|
|
631
|
+
The time elapsed since the last call to ``update_texture``.
|
|
632
|
+
"""
|
|
633
|
+
source = self.source
|
|
634
|
+
_time = self.time
|
|
635
|
+
|
|
636
|
+
frame_rate = source.video_format.frame_rate
|
|
637
|
+
frame_duration = 1 / frame_rate
|
|
638
|
+
ts = source.get_next_video_timestamp()
|
|
639
|
+
# Allow up to frame_duration difference
|
|
640
|
+
while ts is not None and ts + frame_duration < _time:
|
|
641
|
+
source.get_next_video_frame() # Discard frame
|
|
642
|
+
ts = source.get_next_video_timestamp()
|
|
643
|
+
|
|
644
|
+
if ts is None:
|
|
645
|
+
# No more video frames to show. End of video stream.
|
|
646
|
+
pyglet.clock.schedule_once(self._video_finished, 0)
|
|
647
|
+
return
|
|
648
|
+
if ts > _time:
|
|
649
|
+
# update_texture called too early (probably manually!)
|
|
650
|
+
pyglet.clock.schedule_once(self.update_texture, ts - _time)
|
|
651
|
+
return
|
|
652
|
+
|
|
653
|
+
image = source.get_next_video_frame()
|
|
654
|
+
if image is not None:
|
|
655
|
+
with self._context:
|
|
656
|
+
if self._texture is None:
|
|
657
|
+
self._create_texture()
|
|
658
|
+
self._texture.bind()
|
|
659
|
+
self._texture.upload(image, 0, 0, 0)
|
|
660
|
+
|
|
661
|
+
ts = source.get_next_video_timestamp()
|
|
662
|
+
if ts is None:
|
|
663
|
+
delay = frame_duration
|
|
664
|
+
else:
|
|
665
|
+
delay = ts - _time
|
|
666
|
+
|
|
667
|
+
delay = max(0.0, delay)
|
|
668
|
+
pyglet.clock.schedule_once(self.update_texture, delay)
|
|
669
|
+
|
|
670
|
+
def _video_finished(self, _dt: float) -> None:
|
|
671
|
+
if self._audio_player is None:
|
|
672
|
+
self.dispatch_event("on_eos")
|
|
673
|
+
|
|
674
|
+
@property
|
|
675
|
+
def texture(self) -> Texture | None:
|
|
676
|
+
"""Get the texture for the current video frame, if any.
|
|
677
|
+
|
|
678
|
+
You should query this property every time you display a frame of video,
|
|
679
|
+
as multiple textures might be used. This property will be ``None`` if
|
|
680
|
+
the current Source does not contain video.
|
|
681
|
+
"""
|
|
682
|
+
return self._texture
|
|
683
|
+
|
|
684
|
+
def seek_next_frame(self) -> None:
|
|
685
|
+
"""Step forwards one video frame in the current source."""
|
|
686
|
+
ts = self.source.get_next_video_timestamp()
|
|
687
|
+
if ts is None:
|
|
688
|
+
return
|
|
689
|
+
self.seek(ts)
|
|
690
|
+
|
|
691
|
+
def delete(self) -> None:
|
|
692
|
+
super().delete()
|
|
693
|
+
if self._texture is not None:
|
|
694
|
+
self._texture = None
|
|
695
|
+
if self._sprite is not None:
|
|
696
|
+
self._sprite = None
|
|
697
|
+
|
|
698
|
+
|
|
620
699
|
class PlayerGroup:
|
|
621
700
|
"""Group of players that can be played and paused simultaneously.
|
|
622
701
|
|
|
@@ -625,14 +704,13 @@ class PlayerGroup:
|
|
|
625
704
|
All players in the group must currently not belong to any other group.
|
|
626
705
|
"""
|
|
627
706
|
|
|
628
|
-
def __init__(self, players: Iterable[
|
|
707
|
+
def __init__(self, players: Iterable[AudioPlayer]) -> None:
|
|
629
708
|
"""Initialize the PlayerGroup with the players."""
|
|
630
709
|
self.players = list(players)
|
|
631
710
|
|
|
632
711
|
def play(self) -> None:
|
|
633
712
|
"""Begin playing all players in the group simultaneously."""
|
|
634
|
-
audio_players = [p._audio_player
|
|
635
|
-
for p in self.players if p._audio_player]
|
|
713
|
+
audio_players = [p._audio_player for p in self.players if p._audio_player]
|
|
636
714
|
if audio_players:
|
|
637
715
|
audio_players[0]._play_group(audio_players)
|
|
638
716
|
for player in self.players:
|
|
@@ -640,9 +718,14 @@ class PlayerGroup:
|
|
|
640
718
|
|
|
641
719
|
def pause(self) -> None:
|
|
642
720
|
"""Pause all players in the group simultaneously."""
|
|
643
|
-
audio_players = [p._audio_player
|
|
644
|
-
for p in self.players if p._audio_player]
|
|
721
|
+
audio_players = [p._audio_player for p in self.players if p._audio_player]
|
|
645
722
|
if audio_players:
|
|
646
723
|
audio_players[0]._stop_group(audio_players)
|
|
647
724
|
for player in self.players:
|
|
648
725
|
player.pause()
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
AudioPlayer.register_event_type('on_eos')
|
|
729
|
+
AudioPlayer.register_event_type('on_player_eos')
|
|
730
|
+
AudioPlayer.register_event_type('on_player_next_source')
|
|
731
|
+
AudioPlayer.register_event_type('on_driver_reset')
|