Class: MPV::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/mpv_ipc/client.rb

Overview

Represents a connection to an ‘mpv` process over an IPC socket.

Defined Under Namespace

Classes: Event, KeyEvent, ObserverEvent, Reply

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(conn) ⇒ Client

Creates a new MPV::Client instance.

Parameters:

  • conn (Object)

    mpv connection descriptor It can be a Unix socket path as a [String], an already connected [Socket] or its raw file descriptor as an [Integer], or a custom IO-like object with standard gets() and puts() methods.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/mpv_ipc/client.rb', line 34

def initialize(conn)
  @socket = case conn
    when String then UNIXSocket.new(@path = conn)
    when Integer then Socket.for_fd(conn)
    else conn
  end
  @request_id = Concurrent::AtomicFixnum.new(MIN_REQ_ID - 1)
  @reply_queue = MultiQueue.new(RESPONSE_TIMEOUT)
  @event_listeners = Concurrent::Hash.new
  @property_observers = Concurrent::Hash.new
  @message_handlers = Concurrent::Hash.new
  @keybinding_handlers = Concurrent::Hash.new
  @osd_messages = Concurrent::Hash.new
  @event_queue = Queue.new
  @cmd_mutex = Mutex.new
  @keybinding_mutex = Mutex.new
  @osd_mutex = Mutex.new
  @shutdown_callback = nil
  @link_thread = Thread.new(&method(:link_loop))
  @link_thread.name = "mpv link"
  @event_thread = Thread.new(&method(:event_loop))
  @event_thread.name = "mpv event"
end

Class Method Details

.scriptMPV::Client

Alternative constructor for mpv embedded script mode.

Returns:

  • (MPV::Client)

    a new instance of this class using a file descriptor from ‘–mpv-ipc-fd` argument

Raises:

  • (ArgumentError)


21
22
23
24
25
26
27
# File 'lib/mpv_ipc/client.rb', line 21

def self.script
  require "optparse"
  OptionParser.new do |opts|
    opts.on("--mpv-ipc-fd=N", Integer){ |fd| return new(fd) }
  end.parse!
  raise ArgumentError, "--mpv-ipc-fd argument not provided"
end

Instance Method Details

#alive?Boolean

Checks whether the mpv connection is still alive.

Returns:

  • (Boolean)

    true if the connection is alive



60
61
62
# File 'lib/mpv_ipc/client.rb', line 60

def alive?
  @link_thread.alive?
end

#clear_osd_messagesvoid

This method returns an undefined value.

Deletes all messages on the OSD.

Raises:



349
350
351
352
353
354
355
356
357
358
# File 'lib/mpv_ipc/client.rb', line 349

def clear_osd_messages
  @osd_mutex.synchronize do
    unless @osd_messages.empty?
      @osd_messages.each_value{ |(_, scheduler)| scheduler&.cancel }
      @osd_messages.clear
      render_osd_messages
    end
  end
  nil
end

#command(*args) ⇒ Reply

Sends a command to the ‘mpv` process.

Examples:

client.command("loadfile", "mymovie.mp4", "append-play").success? # => true
client.command("asdfgh", "myparam").success? # => false
client.command("asdfgh", "myparam").error # => "invalid parameter"

Parameters:

  • args (Array)

    the individual command arguments to send

Returns:

  • (Reply)

    mpv’s response struct

Raises:



101
102
103
104
105
106
107
108
109
110
# File 'lib/mpv_ipc/client.rb', line 101

def command(*args)
  raise ReleasedConnectionError unless alive?
  @reply_queue.open(next_id) do |queue|
    payload = { command: args, request_id: queue.id, async: true }
    @cmd_mutex.synchronize{ @socket.puts(JSON.fast_generate(payload)) }
    queue.pop
  end
rescue IOError, SystemCallError => exc
  raise ConnectionError, exc
end

#command!(*args) ⇒ Object

Sends a command to the ‘mpv` process and then checks if it was successful.

Examples:

client.command!("loadfile", "mymovie.mp4", "append-play") # => nil
client.command!("asdfgh", "myparam") # => raises MPV::ReplyError

Parameters:

  • args (Array)

    the individual command arguments to send

Returns:

  • (Object)

    mpv’s response data value

Raises:



122
123
124
# File 'lib/mpv_ipc/client.rb', line 122

def command!(*args)
  command(*args).data!
end

#create_osd_message(text, timeout: nil) ⇒ Integer

Creates a new message and adds it to the OSD.

Parameters:

  • text (String, Ass::Text)

    message

  • timeout (Numeric) (defaults to: nil)

    optional timeout in seconds to autoremove the message If omitted or non-positive, the message is not scheduled for automatic removal.

Returns:

  • (Integer)

    message id

Raises:



287
288
289
290
291
292
293
294
295
# File 'lib/mpv_ipc/client.rb', line 287

def create_osd_message(text, timeout: nil)
  id = next_id
  @osd_messages[id] = nil, nil
  edit_osd_message(id, text, timeout: timeout)
  id
rescue
  @osd_messages.delete(id)
  raise
end

#delete_osd_message(id) ⇒ void

This method returns an undefined value.

Deletes one of the messages on the OSD.

Parameters:

  • id (Integer)

    message id

Raises:



336
337
338
339
340
341
342
343
344
# File 'lib/mpv_ipc/client.rb', line 336

def delete_osd_message(id)
  @osd_mutex.synchronize do
    if record = @osd_messages.delete(id)
      record[1]&.cancel
      render_osd_messages
    end
  end
  nil
end

#edit_osd_message(id, text, timeout: nil) ⇒ Boolean

Edits one of the messages on the OSD.

Parameters:

  • id (Integer)

    message id

  • text (String, Ass::Text)

    message

  • timeout (Numeric) (defaults to: nil)

    optional timeout in seconds to autoremove the message If omitted, the timeout remains unchanged. If non-positive, autoremove is disabled.

Returns:

  • (Boolean)

    true if the message with the given id still existed

Raises:



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/mpv_ipc/client.rb', line 304

def edit_osd_message(id, text, timeout: nil)
  text = Ass::Text.new(text) unless text.is_a?(Ass::Text)
  @osd_mutex.synchronize do
    if record = @osd_messages[id]
      record[0] = text.to_script
      render_osd_messages
      if timeout
        if timeout.positive?
          unless record[1]&.reschedule(timeout)
            record[1] = task = Concurrent::ScheduledTask.execute(timeout) do
              @osd_mutex.synchronize do
                if @osd_messages.dig(id, 1) == task
                  @osd_messages.delete(id)
                  render_osd_messages
                end
              end
            end
          end
        elsif record[1]
          record[1].cancel
          record[1] = nil
        end
      end
    end
    !!record
  end
end

#enter_modal_mode(message, keys, exit_key: "ESC") {|KeyEvent| ... } ⇒ String

Enters a modal mode, similar to Vim’s modes, but exits after a single keypress or when the exit key is pressed.

Examples:

client.register_keybindings(%w[d]) do
  client.enter_modal_mode("really delete?", %w[y n]) do |key_event|
    key_event.press? # => true
    key_event.key # => "y"
  end
end

Parameters:

  • message (String)

    message to show while the modal mode is active

  • keys (Array<String>)

    the keys to bind (in input.conf format)

  • exit_key (String) (defaults to: "ESC")

    the key to exit modal mode (in input.conf format)

Yields:

Returns:

  • (String)

    section name (for unregister_keybindings)

Raises:



375
376
377
378
379
380
381
382
383
384
385
# File 'lib/mpv_ipc/client.rb', line 375

def enter_modal_mode(message, keys, exit_key: "ESC", &block)
  osd_id = create_osd_message(message)
  register_keybindings(*keys, exit_key, flags: :force) do |event|
    delete_osd_message(osd_id) rescue nil
    unregister_keybindings(event.section) rescue nil
    block.call(event) if block_given? && event.key != exit_key
  end
rescue
  delete_osd_message(osd_id) rescue nil
  raise
end

#get_property(name) ⇒ Reply

Retrieves a property from the ‘mpv` process.

Examples:

client.get_property("pause").data # => true
client.get_property("volume").data # => 100.0
client.get_property("asdfgh").data # => nil
client.get_property("asdfgh").error # => "property not found"

Parameters:

  • name (String)

    the property name (e.g.: volume)

Returns:

  • (Reply)

    mpv’s response struct



134
135
136
# File 'lib/mpv_ipc/client.rb', line 134

def get_property(name)
  command("get_property", name)
end

#get_property!(name) ⇒ Object

Retrieves a property from the ‘mpv` process and then confirms success.

Examples:

client.get_property!("pause") # => true
client.get_property!("volume") # => 100.0
client.get_property!("asdfgh") # => raises MPV::ReplyError

Parameters:

  • name (String)

    the property name (e.g.: volume)

Returns:

  • (Object)

    mpv’s response data value

Raises:



146
147
148
# File 'lib/mpv_ipc/client.rb', line 146

def get_property!(name)
  get_property(name).data!
end

#observe_property(name) {|ObserverEvent| ... } ⇒ Integer

Observes property changes.

Examples:

client.observe_property("volume") do |event|
 puts "the new volume is #{event.data}"
end

Parameters:

  • name (String)

    name of the property to observe

Yields:

Returns:

  • (Integer)

    the observer id to use with unobserve_property

Raises:



185
186
187
188
189
190
191
192
193
# File 'lib/mpv_ipc/client.rb', line 185

def observe_property(name, &block)
  id = next_id
  @property_observers[id] = block
  command!("observe_property", id, name)
  id
rescue
  @property_observers.delete(id)
  raise
end

#quit!Boolean

Sends a ‘quit` command to shut down the `mpv` process and releases the communication with it.

Returns:

  • (Boolean)

    true if the ‘quit` command was sent successfully



81
82
83
84
85
86
87
88
89
# File 'lib/mpv_ipc/client.rb', line 81

def quit!
  command!("quit")
  @link_thread.join
  true
rescue
  @link_thread.exit
  @link_thread.join
  false
end

#register_event_listener(name = "") {|Event| ... } ⇒ void

This method returns an undefined value.

Registers an event listener.

Parameters:

  • name (String) (defaults to: "")

    event name to listen for (defaults to all)

Yields:

  • (Event)

    called with the event descriptor object



208
209
210
# File 'lib/mpv_ipc/client.rb', line 208

def register_event_listener(name="", &block)
  @event_listeners[name] = block
end

#register_keybindings(*keys, section: nil, flags: :default) {|KeyEvent| ... } ⇒ String

Registers a keybinding section.

Examples:

client.register_keybindings("a", "b") do |key_event|
  key_event.hold? # => true
  key_event.key # => "b"
end

client.command("keypress", "b")

Parameters:

  • keys (Array<String>)

    key names to bind (in input.conf format)

  • section (String) (defaults to: nil)

    optional custom section name

  • flags (Symbol) (defaults to: :default)

    optional key binding priority (default: :default) :default lets user bindings win, while :force overrides them

Yields:

Returns:

  • (String)

    section name (for unregister_keybindings)

Raises:



255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/mpv_ipc/client.rb', line 255

def register_keybindings(*keys, section: nil, flags: :default, &block)
  section ||= "sect_#{next_id}"
  contents = keys.flatten.map{ |key| "#{key} script-binding #{client_name}/#{section}" }
  @keybinding_mutex.synchronize do
    @keybinding_handlers[section] = block
    command!("define-section", section, contents.join("\n"), flags)
    command!("enable-section", section)
  rescue
    @keybinding_handlers.delete(section)
    raise
  end
  section
end

#register_message_handler(message) {|Array| ... } ⇒ void

This method returns an undefined value.

Registers a client-message handler.

Examples:

client.register_message_handler("cool-message") do |a, b|
  puts "hello #{a}-#{b}" # => hello, mikuru-chan
end

client.command("script-message", "cool-message", "mikuru", "chan")

Parameters:

  • message (String)

    the script-message identifier

Yields:

  • (Array)

    called with the arguments passed to client-message



229
230
231
# File 'lib/mpv_ipc/client.rb', line 229

def register_message_handler(message, &block)
  @message_handlers[message] = block
end

#register_shutdown_callback {|Boolean| ... } ⇒ void

This method returns an undefined value.

Registers a shutdown callback for notification or cleanup.

Yields:

  • (Boolean)

    called after the mpv connection is closed with true if the server initiated the socket closure



391
392
393
# File 'lib/mpv_ipc/client.rb', line 391

def register_shutdown_callback(&block)
  @shutdown_callback = block
end

#release!void

This method returns an undefined value.

Terminates the mpv communication.



73
74
75
76
# File 'lib/mpv_ipc/client.rb', line 73

def release!
  @link_thread.exit
  @link_thread.join
end

#set_property(name, value) ⇒ Reply

Sends a property change to the ‘mpv` process.

Examples:

client.set_property("pause", true).success? # => true
client.set_property("volume", 100.0).success? # => true
client.set_property("asdfgh", 42).success? # => false
client.set_property("asdfgh", 42).error # => "property not found"

Parameters:

  • name (String)

    the property name (e.g.: volume)

  • value (Object)

    property value

Returns:

  • (Reply)

    mpv’s response struct



159
160
161
# File 'lib/mpv_ipc/client.rb', line 159

def set_property(name, value)
  command("set_property", name, value)
end

#set_property!(name, value) ⇒ Object

Sends a property change to the ‘mpv` process and then confirms success.

Examples:

client.set_property!("pause", true) # => nil
client.set_property!("volume", 100.0) # => nil
client.set_property!("asdfgh", 42) # => raises MPV::ReplyError

Parameters:

  • name (String)

    the property name (e.g.: volume)

  • value (Object)

    property value

Returns:

  • (Object)

    mpv’s response data value

Raises:



172
173
174
# File 'lib/mpv_ipc/client.rb', line 172

def set_property!(name, value)
  set_property(name, value).data!
end

#unobserve_property(id) ⇒ void

This method returns an undefined value.

Unobserves property changes.

Parameters:

  • id (Integer)

    the return value of #observe_property

Raises:



199
200
201
202
# File 'lib/mpv_ipc/client.rb', line 199

def unobserve_property(id)
  command!("unobserve_property", id)
  @property_observers.delete(id)
end

#unregister_event_listener(name = "") ⇒ void

This method returns an undefined value.

Unregisters an event listener.

Parameters:

  • name (String) (defaults to: "")

    name of the listened event (defaults to all)



215
216
217
# File 'lib/mpv_ipc/client.rb', line 215

def unregister_event_listener(name="")
  @event_listeners.delete(name)
end

#unregister_keybindings(section) ⇒ void

This method returns an undefined value.

Unregisters a keybinding section.

Parameters:

  • section (String)

    section name

Raises:



273
274
275
276
277
278
279
# File 'lib/mpv_ipc/client.rb', line 273

def unregister_keybindings(section)
  @keybinding_mutex.synchronize do
    command!("disable-section", section)
    command!("define-section", section, "")
    @keybinding_handlers.delete(section)
  end
end

#unregister_message_handler(message) ⇒ void

This method returns an undefined value.

Unregisters a client-message handler.

Parameters:

  • message (String)

    the client-message identifier



236
237
238
# File 'lib/mpv_ipc/client.rb', line 236

def unregister_message_handler(message)
  @message_handlers.delete(message)
end

#unregister_shutdown_callbackvoid

This method returns an undefined value.

Unregisters the current shutdown callback.



397
398
399
# File 'lib/mpv_ipc/client.rb', line 397

def unregister_shutdown_callback
  @shutdown_callback = nil
end

#wait(timeout = nil) ⇒ Boolean

Waits for the mpv connection to terminate.

Parameters:

  • timeout (Numeric) (defaults to: nil)

    optional timeout in seconds

Returns:

  • (Boolean)

    whether the connection was terminated



67
68
69
# File 'lib/mpv_ipc/client.rb', line 67

def wait(timeout=nil)
  !!@link_thread.join(timeout)
end