Supabase MCP Server
- video-player
import AVFoundation
import AVKit
import AppKit
import Foundation
NSApplication.shared.activate(ignoringOtherApps: true)
class VideoPlayerDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
var playerView: AVPlayerView!
var player: AVPlayer!
var queuePlayer: AVQueuePlayer!
var playerLooper: AVPlayerLooper?
// Playlist management
struct VideoEntry {
let path: String
let videoName: String
}
var videos: [VideoEntry] = []
var currentVideoIndex: Int = 0
// UI Elements
var controlsView: NSView!
var previousButton: NSButton!
var nextButton: NSButton!
var videoLabel: NSTextField!
func applicationDidFinishLaunching(_ notification: Notification) {
// Need at least a video name and video path pair
guard CommandLine.arguments.count > 2 else {
print("Usage: vj-player \"Video Name 1\" video1.mp4 \"Video Name 2\" video2.mp4 ...")
NSApplication.shared.terminate(nil)
return
}
// Parse arguments into video entries
let args = Array(CommandLine.arguments.dropFirst())
if args.count % 2 != 0 {
print("Error: Each video must have a name")
NSApplication.shared.terminate(nil)
return
}
// Create video entries from pairs of arguments
for i in stride(from: 0, to: args.count, by: 2) {
videos.append(VideoEntry(path: args[i + 1], videoName: args[i]))
}
// Create the window
let windowRect = NSRect(x: 0, y: 0, width: 800, height: 650)
window = NSWindow(
contentRect: windowRect,
styleMask: [.titled, .closable, .miniaturizable, .resizable],
backing: .buffered,
defer: false
)
window.level = .floating
// Create the main container view
let containerView = NSView(frame: windowRect)
window.contentView = containerView
// Create the player view
let playerRect = NSRect(x: 0, y: 50, width: windowRect.width, height: windowRect.height - 50)
playerView = AVPlayerView(frame: playerRect)
playerView.autoresizingMask = [.width, .height]
playerView.controlsStyle = .floating
playerView.showsFullScreenToggleButton = true
containerView.addSubview(playerView)
// Create controls view
let controlsRect = NSRect(x: 0, y: 0, width: windowRect.width, height: 50)
controlsView = NSView(frame: controlsRect)
controlsView.autoresizingMask = [.width]
containerView.addSubview(controlsView)
// Create navigation buttons
previousButton = NSButton(frame: NSRect(x: 10, y: 10, width: 100, height: 30))
previousButton.title = "Previous"
previousButton.bezelStyle = .rounded
previousButton.target = self
previousButton.action = #selector(previousVideo)
controlsView.addSubview(previousButton)
nextButton = NSButton(frame: NSRect(x: 120, y: 10, width: 100, height: 30))
nextButton.title = "Next"
nextButton.bezelStyle = .rounded
nextButton.target = self
nextButton.action = #selector(nextVideo)
controlsView.addSubview(nextButton)
// Create video label
videoLabel = NSTextField(frame: NSRect(x: 230, y: 15, width: 500, height: 20))
videoLabel.isEditable = false
videoLabel.isBordered = false
videoLabel.backgroundColor = .clear
videoLabel.font = NSFont.systemFont(ofSize: 14, weight: .bold)
controlsView.addSubview(videoLabel)
// Setup window
window.title = "Video Player"
window.center()
window.makeKeyAndOrderFront(nil)
// Start playing first video
playCurrentVideo()
// Set up keyboard event monitoring
NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in
self.handleKeyEvent(event)
return event
}
// Print controls
print("Controls:")
print("Space: Play/Pause")
print("Left Arrow: Seek backward 10 seconds")
print("Right Arrow: Seek forward 10 seconds")
print("N: Next video")
print("P: Previous video")
print("Q: Quit")
}
func playCurrentVideo() {
guard currentVideoIndex >= 0 && currentVideoIndex < videos.count else {
print("Invalid video index")
return
}
let videoEntry = videos[currentVideoIndex]
let videoURL = URL(fileURLWithPath: videoEntry.path)
// Create a new player item
let playerItem = AVPlayerItem(url: videoURL)
// Create or reuse queue player
if queuePlayer == nil {
queuePlayer = AVQueuePlayer()
playerView.player = queuePlayer
}
// Remove existing looper if any
playerLooper?.disableLooping()
playerLooper = nil
// Create new looper
playerLooper = AVPlayerLooper(player: queuePlayer, templateItem: playerItem)
// Update window title and video label
window.title = "Video Jungle Player - \(videoURL.lastPathComponent) [\(currentVideoIndex + 1)/\(videos.count)]"
videoLabel.stringValue = videoEntry.videoName
// Update button states
previousButton.isEnabled = currentVideoIndex > 0
nextButton.isEnabled = currentVideoIndex < videos.count - 1
queuePlayer.play()
}
@objc func previousVideo() {
if currentVideoIndex > 0 {
currentVideoIndex -= 1
playCurrentVideo()
}
}
@objc func nextVideo() {
if currentVideoIndex < videos.count - 1 {
currentVideoIndex += 1
playCurrentVideo()
}
}
func handleKeyEvent(_ event: NSEvent) {
guard let characters = event.characters else { return }
switch characters {
case " ":
// Toggle play/pause
if queuePlayer.rate == 0 {
queuePlayer.play()
print("Playing")
} else {
queuePlayer.pause()
print("Paused")
}
case String(Character(UnicodeScalar(NSLeftArrowFunctionKey)!)):
// Seek backward 10 seconds
let currentTime = queuePlayer.currentTime()
let newTime = CMTimeAdd(currentTime, CMTime(seconds: -10, preferredTimescale: 1))
queuePlayer.seek(to: newTime)
print("Seeking backward 10 seconds")
case String(Character(UnicodeScalar(NSRightArrowFunctionKey)!)):
// Seek forward 10 seconds
let currentTime = queuePlayer.currentTime()
let newTime = CMTimeAdd(currentTime, CMTime(seconds: 10, preferredTimescale: 1))
queuePlayer.seek(to: newTime)
print("Seeking forward 10 seconds")
case "n", "N":
nextVideo()
case "p", "P":
previousVideo()
case "q", "Q":
NSApplication.shared.terminate(nil)
default:
break
}
}
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
}
// Create and start the application
let delegate = VideoPlayerDelegate()
let app = NSApplication.shared
app.delegate = delegate
app.run()