This repository has been archived on 2022-06-09. You can view files and clone it, but cannot push or open issues or pull requests.
mpv_syndicate/src/mpv_syndicate.nim

120 lines
3.8 KiB
Nim

# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, json, osproc, sets, tables]
import preserves, preserves/parse
import syndicate, syndicate/[actors, capabilities, dataspaces, patterns, relay]
from std/os import getCurrentProcessId, commandLineParams, extractFilename, paramStr, sleep
proc mint(): SturdyRef =
var key: array[16, byte]
mint(key, "syndicate")
type
VideoEnqueue* {.preservesRecord: "playvideo".} = object
uri*: string
VideoEnqueued* {.preservesRecord: "video-enqueued".} = object
uri*: string
id*: int
import std/net
from std/nativesockets import AF_UNIX, SOCK_DGRAM, Protocol
type MpvProcess = ref object
process: Process
socket: Socket # TODO AsyncSocket
proc newMpvProcess(): MpvProcess =
let socketPath = "/tmp/mpvsocket-" & $getCurrentProcessId()
result = MpvProcess(process: startProcess("mpv",
args = ["--idle", "--input-ipc-server=" & socketPath],
options = {poUsePath, poParentStreams}))
sleep 500
result.socket = newSocket(
domain = AF_UNIX,
sockType = SOCK_STREAM,
protocol = cast[Protocol](0),
buffered = false)
connectUnix(result.socket, socketPath)
proc recv(mpv: MpvProcess): JsonNode =
mpv.socket.recvLine.parseJson
proc send(mpv: MpvProcess; command: varargs[string, `$`]): JsonNode =
let js = %* {"command": command}
mpv.socket.send($js & "\n")
while true: # TODO: dispatch responses asynchronously
let resp = mpv.recv
if resp.kind == JObject and resp.contains "error":
let errmsg = getStr resp["error"]
if errmsg != "success":
raise newException(CatchableError, errmsg)
result = resp["data"]
break
proc playlist(mpv: MpvProcess): JsonNode =
result = mpv.send("get_property", "playlist")
proc enqueue(mpv: MpvProcess; uri: string): int =
let resp = mpv.send("loadfile", uri, "append-play")
getInt resp["playlist_entry_id"]
proc frontend() =
proc getUriParams(): seq[string] =
result = commandLineParams()
if result.len == 0:
result.add execProcess"wl-paste"
# take the clipboard if nothing on the command line
waitFor runActor("chat") do (turn: var Turn):
let cap = mint()
connectUnix(turn, "/run/syndicate/ds", cap) do (turn: var Turn; a: Assertion) -> TurnAction:
let
ds = unembed a
uriToAssertions = newTable[string, Handle]()
# TODO: assert a dependency on a video playback service
for uri in getUriParams():
uriToAssertions[uri] = publish(turn, ds, VideoEnqueue(uri: uri))
onPublish(turn, ds, VideoEnqueued ? { 0: `?*`(), 1: `?*`() }) do (uri: string; id: int):
# querying when a video is enqueued implies that it should be enqueued?
echo uri, " enqueued with id ", id
var handle: Handle
if pop(uriToAssertions, uri, handle):
retract(turn, handle)
if uriToAssertions.len == 0:
stopActor(turn)
proc backend() =
waitFor runActor("chat") do (turn: var Turn):
let cap = mint()
connectUnix(turn, "/run/syndicate/ds", cap) do (turn: var Turn; a: Assertion) -> TurnAction:
let
ds = unembed a
mpv = newMpvProcess()
playlistAssertions = newTable[int, Handle]()
onPublish(turn, ds, VideoEnqueue ? { 0: `?*`() }) do (uri: string):
let id = mpv.enqueue(uri)
playlistAssertions[id] = publish(turn, ds, VideoEnqueued(uri: uri, id: id))
block:
# retract assertions not corresponding to the playlist
# TODO: do this from asyncronous mpv IPC events
var idSet: HashSet[int]
for e in mpv.playlist:
idSet.incl getInt(e["id"])
for (id, handle) in playlistAssertions.pairs:
if id notin idSet: retract(turn, handle)
case extractFilename(paramStr 0)
of "mpv_syndicate": backend()
else: frontend()