Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reorganization - RC interface integration #2

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The dependencies will be made available in the `env` directory.
3) Set the following env vars:

```
VLC_LRS_ENDPOINT=<lrs-endpoint>
VLC_LRS_DOMAIN=<lrs-endpoint>
VLC_LRS_KEY=<lrs-key>
VLC_LRS_SECRET=<lrs-secret>
```
Expand Down
166 changes: 2 additions & 164 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,164 +1,2 @@
certifi==2024.7.4
charset-normalizer==3.3.2
idna==3.8
pyobjc==10.3.1
pyobjc-core==10.3.1
pyobjc-framework-Accessibility==10.3.1
pyobjc-framework-Accounts==10.3.1
pyobjc-framework-AddressBook==10.3.1
pyobjc-framework-AdServices==10.3.1
pyobjc-framework-AdSupport==10.3.1
pyobjc-framework-AppleScriptKit==10.3.1
pyobjc-framework-AppleScriptObjC==10.3.1
pyobjc-framework-ApplicationServices==10.3.1
pyobjc-framework-AppTrackingTransparency==10.3.1
pyobjc-framework-AudioVideoBridging==10.3.1
pyobjc-framework-AuthenticationServices==10.3.1
pyobjc-framework-AutomaticAssessmentConfiguration==10.3.1
pyobjc-framework-Automator==10.3.1
pyobjc-framework-AVFoundation==10.3.1
pyobjc-framework-AVKit==10.3.1
pyobjc-framework-AVRouting==10.3.1
pyobjc-framework-BackgroundAssets==10.3.1
pyobjc-framework-BrowserEngineKit==10.3.1
pyobjc-framework-BusinessChat==10.3.1
pyobjc-framework-CalendarStore==10.3.1
pyobjc-framework-CallKit==10.3.1
pyobjc-framework-CFNetwork==10.3.1
pyobjc-framework-Cinematic==10.3.1
pyobjc-framework-ClassKit==10.3.1
pyobjc-framework-CloudKit==10.3.1
pyobjc-framework-Cocoa==10.3.1
pyobjc-framework-Collaboration==10.3.1
pyobjc-framework-ColorSync==10.3.1
pyobjc-framework-Contacts==10.3.1
pyobjc-framework-ContactsUI==10.3.1
pyobjc-framework-CoreAudio==10.3.1
pyobjc-framework-CoreAudioKit==10.3.1
pyobjc-framework-CoreBluetooth==10.3.1
pyobjc-framework-CoreData==10.3.1
pyobjc-framework-CoreHaptics==10.3.1
pyobjc-framework-CoreLocation==10.3.1
pyobjc-framework-CoreMedia==10.3.1
pyobjc-framework-CoreMediaIO==10.3.1
pyobjc-framework-CoreMIDI==10.3.1
pyobjc-framework-CoreML==10.3.1
pyobjc-framework-CoreMotion==10.3.1
pyobjc-framework-CoreServices==10.3.1
pyobjc-framework-CoreSpotlight==10.3.1
pyobjc-framework-CoreText==10.3.1
pyobjc-framework-CoreWLAN==10.3.1
pyobjc-framework-CryptoTokenKit==10.3.1
pyobjc-framework-DataDetection==10.3.1
pyobjc-framework-DeviceCheck==10.3.1
pyobjc-framework-DictionaryServices==10.3.1
pyobjc-framework-DiscRecording==10.3.1
pyobjc-framework-DiscRecordingUI==10.3.1
pyobjc-framework-DiskArbitration==10.3.1
pyobjc-framework-DVDPlayback==10.3.1
pyobjc-framework-EventKit==10.3.1
pyobjc-framework-ExceptionHandling==10.3.1
pyobjc-framework-ExecutionPolicy==10.3.1
pyobjc-framework-ExtensionKit==10.3.1
pyobjc-framework-ExternalAccessory==10.3.1
pyobjc-framework-FileProvider==10.3.1
pyobjc-framework-FileProviderUI==10.3.1
pyobjc-framework-FinderSync==10.3.1
pyobjc-framework-FSEvents==10.3.1
pyobjc-framework-GameCenter==10.3.1
pyobjc-framework-GameController==10.3.1
pyobjc-framework-GameKit==10.3.1
pyobjc-framework-GameplayKit==10.3.1
pyobjc-framework-HealthKit==10.3.1
pyobjc-framework-ImageCaptureCore==10.3.1
pyobjc-framework-InputMethodKit==10.3.1
pyobjc-framework-InstallerPlugins==10.3.1
pyobjc-framework-InstantMessage==10.3.1
pyobjc-framework-Intents==10.3.1
pyobjc-framework-IntentsUI==10.3.1
pyobjc-framework-IOBluetooth==10.3.1
pyobjc-framework-IOBluetoothUI==10.3.1
pyobjc-framework-IOSurface==10.3.1
pyobjc-framework-iTunesLibrary==10.3.1
pyobjc-framework-KernelManagement==10.3.1
pyobjc-framework-LatentSemanticMapping==10.3.1
pyobjc-framework-LaunchServices==10.3.1
pyobjc-framework-libdispatch==10.3.1
pyobjc-framework-libxpc==10.3.1
pyobjc-framework-LinkPresentation==10.3.1
pyobjc-framework-LocalAuthentication==10.3.1
pyobjc-framework-LocalAuthenticationEmbeddedUI==10.3.1
pyobjc-framework-MailKit==10.3.1
pyobjc-framework-MapKit==10.3.1
pyobjc-framework-MediaAccessibility==10.3.1
pyobjc-framework-MediaLibrary==10.3.1
pyobjc-framework-MediaPlayer==10.3.1
pyobjc-framework-MediaToolbox==10.3.1
pyobjc-framework-Metal==10.3.1
pyobjc-framework-MetalFX==10.3.1
pyobjc-framework-MetalKit==10.3.1
pyobjc-framework-MetalPerformanceShaders==10.3.1
pyobjc-framework-MetalPerformanceShadersGraph==10.3.1
pyobjc-framework-MetricKit==10.3.1
pyobjc-framework-MLCompute==10.3.1
pyobjc-framework-ModelIO==10.3.1
pyobjc-framework-MultipeerConnectivity==10.3.1
pyobjc-framework-NaturalLanguage==10.3.1
pyobjc-framework-NetFS==10.3.1
pyobjc-framework-Network==10.3.1
pyobjc-framework-NetworkExtension==10.3.1
pyobjc-framework-NotificationCenter==10.3.1
pyobjc-framework-OpenDirectory==10.3.1
pyobjc-framework-OSAKit==10.3.1
pyobjc-framework-OSLog==10.3.1
pyobjc-framework-PassKit==10.3.1
pyobjc-framework-PencilKit==10.3.1
pyobjc-framework-PHASE==10.3.1
pyobjc-framework-Photos==10.3.1
pyobjc-framework-PhotosUI==10.3.1
pyobjc-framework-PreferencePanes==10.3.1
pyobjc-framework-PushKit==10.3.1
pyobjc-framework-Quartz==10.3.1
pyobjc-framework-QuickLookThumbnailing==10.3.1
pyobjc-framework-ReplayKit==10.3.1
pyobjc-framework-SafariServices==10.3.1
pyobjc-framework-SafetyKit==10.3.1
pyobjc-framework-SceneKit==10.3.1
pyobjc-framework-ScreenCaptureKit==10.3.1
pyobjc-framework-ScreenSaver==10.3.1
pyobjc-framework-ScreenTime==10.3.1
pyobjc-framework-ScriptingBridge==10.3.1
pyobjc-framework-SearchKit==10.3.1
pyobjc-framework-Security==10.3.1
pyobjc-framework-SecurityFoundation==10.3.1
pyobjc-framework-SecurityInterface==10.3.1
pyobjc-framework-SensitiveContentAnalysis==10.3.1
pyobjc-framework-ServiceManagement==10.3.1
pyobjc-framework-SharedWithYou==10.3.1
pyobjc-framework-SharedWithYouCore==10.3.1
pyobjc-framework-ShazamKit==10.3.1
pyobjc-framework-Social==10.3.1
pyobjc-framework-SoundAnalysis==10.3.1
pyobjc-framework-Speech==10.3.1
pyobjc-framework-SpriteKit==10.3.1
pyobjc-framework-StoreKit==10.3.1
pyobjc-framework-Symbols==10.3.1
pyobjc-framework-SyncServices==10.3.1
pyobjc-framework-SystemConfiguration==10.3.1
pyobjc-framework-SystemExtensions==10.3.1
pyobjc-framework-ThreadNetwork==10.3.1
pyobjc-framework-UniformTypeIdentifiers==10.3.1
pyobjc-framework-UserNotifications==10.3.1
pyobjc-framework-UserNotificationsUI==10.3.1
pyobjc-framework-VideoSubscriberAccount==10.3.1
pyobjc-framework-VideoToolbox==10.3.1
pyobjc-framework-Virtualization==10.3.1
pyobjc-framework-Vision==10.3.1
pyobjc-framework-WebKit==10.3.1
PySide6==6.7.2
PySide6_Addons==6.7.2
PySide6_Essentials==6.7.2
python-vlc==3.0.20123
requests==2.32.3
shiboken6==6.7.2
urllib3==2.2.2
pexpect==4.9.0
Requests==2.32.3
64 changes: 8 additions & 56 deletions xapi_vlc/main.py
Original file line number Diff line number Diff line change
@@ -1,75 +1,27 @@
import sys
import vlc
import argparse
from xapi_vlc.vlc.api import VLCController
from xapi_vlc.xapi import client
from xapi_vlc.xapi import statement
from PySide6.QtWidgets import QApplication, QMainWindow
from PySide6.QtCore import Qt


def make_client(player, userid):

def make_client(userid):
def client_fn(event):
data = statement.create(event, player, userid)
data = statement.create(event, userid)
client.send(data)

return client_fn

class VLCWindow(QMainWindow):
def __init__(self, filepath=None, userid=None):
super().__init__()

# Set up VLC player
self.instance = vlc.Instance()
self.player = self.instance.media_player_new()
media = self.instance.media_new(filepath)
self.player.set_media(media)
self.player.set_nsobject(self.winId())

# Attach Events
# logger = make_logger(self.player)
client = make_client(self.player, userid)
event_manager = self.player.event_manager()
event_manager.event_attach(vlc.EventType.MediaPlayerPlaying, client)
event_manager.event_attach(vlc.EventType.MediaPlayerEndReached, client)
event_manager.event_attach(vlc.EventType.MediaPlayerPaused, client)

# Start playing the media
self.player.play()

# Set the window title and size
self.setWindowTitle("VLC Player")
self.setGeometry(100, 100, 800, 600)

def keyPressEvent(self, event):
"""Handle key press events."""
if event.key() == Qt.Key_Space:
self.toggle_play_pause()

def toggle_play_pause(self):
"""Toggle play/pause of the VLC player."""
if self.player.is_playing():
self.player.pause()
else:
self.player.play()

def closeEvent(self, event):
"""Handle window close event."""
self.player.stop()
event.accept()

def main():
parser = argparse.ArgumentParser(description="VLC Player that sends statements to an LRS.")
parser.add_argument('--content', type=str, required=True, help="Path to content to be played.")
parser.add_argument('--userid', type=str, required=True, help="User ID. (aka email)")

args = parser.parse_args()


app = QApplication(sys.argv)
window = VLCWindow(filepath=args.content, userid=args.userid)
window.show()
sys.exit(app.exec())
client = make_client(args.userid)
controller = VLCController(filepath=args.content)
controller.play()
print("Starting Event Watcher...")
controller.start_event_watcher(client)

if __name__ == "__main__":
main()
Empty file added xapi_vlc/vlc/__init__.py
Empty file.
99 changes: 99 additions & 0 deletions xapi_vlc/vlc/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import sys
import subprocess
import time
import pexpect
import threading
import atexit
import re

# function that strips the data of the command and only returns response.
def parse_response(output):
match = re.search(r'^\w+\r?\n([\s\S]*)', output)
if match:
return match.group(1)
return None

class VLCController:
def __init__(self, filepath=None):
self.vlc_process = pexpect.spawn(f"vlc --extraintf rc {filepath}")
self.previous_state = None
time.sleep(2) # Allow time for VLC to start
self.running = False
self.event_thread = None

# calls stop event watcher when exit occurs
atexit.register(self.stop_event_watcher)

## communications and parsing RC
def flush_output(self):
"""Flush any leftover output from the previous command."""
while self.vlc_process.expect(['>', pexpect.TIMEOUT], timeout=0.1) == 0:
self.vlc_process.before

def send_command(self, command):
self.flush_output()
self.vlc_process.sendline(command)
self.vlc_process.expect('>')
return parse_response(self.vlc_process.before.decode('utf-8').strip())

def get_time(self):
response = self.send_command("get_time")
try:
return int(response)
except TypeError:
return 0

def get_title(self):
response = self.send_command("get_title")
return response

def get_status(self):
return self.send_command("status")

def get_length(self):
response = self.send_command("get_length")
try:
return int(response)
except TypeError:
return 0

def form_metadata_map(self, status):
return {"status": status,
"title": self.get_title(),
"time": self.get_time(),
"length": self.get_length()}

def play(self):
self.send_command("play")


## Event Watching
def parse_status(self, status_output, callback):
if "state playing" in status_output and self.previous_state != "playing":
callback(self.form_metadata_map("playing"))
self.previous_state = "playing"
elif "state paused" in status_output and self.previous_state != "paused":
callback(self.form_metadata_map("paused"))
self.previous_state = "paused"
elif "state stopped" in status_output and self.previous_state != "stopped":
callback(self.form_metadata_map("stopped"))
self.previous_state = "stopped"
elif "state ended" in status_output and self.previous_state != "ended":
callback(self.form_metadata_map("finished"))
self.previous_state = "ended"

def on_state_change(self, callback):
while self.running:
status = self.get_status()
self.parse_status(status, callback)

def start_event_watcher(self, callback):
if not self.running:
self.running = True
self.event_thread = threading.Thread(target=self.on_state_change, args=(callback,))
self.event_thread.start()

def stop_event_watcher(self):
self.running = False
if self.event_thread:
self.event_thread.join()
2 changes: 1 addition & 1 deletion xapi_vlc/xapi/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def send(data):
# get creds go here
lrs_key = os.environ.get('VLC_LRS_KEY')
lrs_secret = os.environ.get('VLC_LRS_SECRET')
url = os.environ.get('VLC_LRS_ENDPOINT')
url = os.environ.get('VLC_LRS_DOMAIN')

headers = {
"Content-Type": "application/json",
Expand Down
Loading