66import time
77from pathlib import Path
88from collections import defaultdict
9+ from functools import partial
910import logging
1011import warnings
1112
2728 'git+https://github.com/int-brain-lab/project_extraction.git"' , RuntimeWarning )
2829
2930
31+ class MediaStats (vlc .MediaStats ):
32+ """A class to store media stats."""
33+
34+ def fieldnames (self ):
35+ """Return the field names."""
36+ return zip (* self ._fields_ )[0 ]
37+
38+ def as_tuple (self ):
39+ """Return all attribute values as a tuple."""
40+ return tuple (map (partial (getattr , self ), self .fieldnames ()))
41+
42+
3043class Player :
3144 """A VLC player."""
3245 def __init__ (self , rate = 1 ):
@@ -35,6 +48,8 @@ def __init__(self, rate=1):
3548 self ._player .set_fullscreen (True )
3649 self ._player .set_rate (rate )
3750 self ._media = None
51+ self ._media_stats = MediaStats ()
52+ self ._stats = []
3853 self .events = defaultdict (list )
3954 em = self ._player .event_manager ()
4055 for event in (vlc .EventType .MediaPlayerPlaying , vlc .EventType .MediaPlayerEndReached ):
@@ -46,6 +61,27 @@ def _record_event(self, event):
4661 # Have to convert to str as object pointer may change
4762 self .events [str (event .type ).split ('.' )[- 1 ]].append (time .time ())
4863
64+ def update_media_stats (self ):
65+ """Update media stats.
66+
67+ Returns
68+ -------
69+ bool
70+ True if the stats have changed since the last update.
71+ """
72+ if not vlc .libvlc_media_get_stats (self ._player .get_media (), self ._media_stats ):
73+ return False
74+ stats = tuple ((time .time (), * self ._media_stats .as_tuple ()))
75+ if not any (self ._stats ) or stats [1 :] != self ._stats [- 1 ][1 :]:
76+ self ._stats .append (stats )
77+ return True
78+ return False
79+
80+ @property
81+ def stats (self ):
82+ """Return media stats."""
83+ return pd .DataFrame (self ._stats , columns = ['time' , * self ._media_stats .fieldnames ()])
84+
4985 def play (self , path ):
5086 """Play a video.
5187
@@ -112,8 +148,10 @@ def __init__(self, **kwargs):
112148 if self .hardware_settings .get ('MAIN_SYNC' , False ):
113149 raise NotImplementedError ('Recording frame2ttl on Bpod not yet implemented' )
114150 self .paths .DATA_FILE_PATH = self .paths .DATA_FILE_PATH .with_name ('_sp_taskData.raw.pqt' )
151+ self .paths .STATS_FILE_PATH = self .paths .DATA_FILE_PATH .with_name ('_sp_videoData.stats.pqt' )
115152 self .video = None
116153 self .trial_num = - 1
154+ self ._log_level = logging .getLevelNamesMapping ()[kwargs .get ('log_level' , 'INFO' )]
117155 columns = ['intervals_0' , 'intervals_1' ]
118156 self .data = pd .DataFrame (pd .NA , index = range (self .task_params .NREPEATS ), columns = columns )
119157
@@ -122,10 +160,13 @@ def save(self):
122160 if self .video :
123161 data = pd .concat ([self .data , pd .DataFrame .from_dict (self .video .events )], axis = 1 )
124162 data .to_parquet (self .paths .DATA_FILE_PATH )
163+ if 20 > self ._log_level > 0 :
164+ stats = self .video .stats
165+ stats .to_parquet (self .paths .STATS_FILE_PATH )
125166 self .paths .SESSION_FOLDER .joinpath ('transfer_me.flag' ).touch ()
126167
127168 def start_hardware (self ):
128- self .start_mixin_bpod () # used for protocol spacer only
169+ self .start_mixin_bpod ()
129170 self .video = Player ()
130171
131172 def next_trial (self ):
@@ -150,15 +191,15 @@ def _set_bpod_out(self, val):
150191
151192 def _run (self ):
152193 """This is the method that runs the video."""
153- # make the bpod send spacer signals to the main sync clock for protocol discovery
154- self .send_spacers ()
155194 for rep in range (self .task_params .NREPEATS ): # Main loop
156195 self .next_trial ()
157196 self ._set_bpod_out (True )
158197 # TODO c.f. MediaListPlayerPlayed event
159198 while not self .video .is_started :
160199 ... # takes time to actually start playback
161200 while self .video .is_playing or (end_time := self .video .get_ended_time (rep )) is None :
201+ if 20 > self ._log_level > 0 :
202+ self .video .update_media_stats ()
162203 time .sleep (0.05 )
163204 # trial finishes when playback finishes
164205 self ._set_bpod_out (False )
0 commit comments