File: video-player.rb

package info (click to toggle)
ruby-gnome 4.2.2-2
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 26,648 kB
  • sloc: ruby: 67,701; ansic: 67,431; xml: 350; sh: 201; cpp: 45; makefile: 42
file content (378 lines) | stat: -rwxr-xr-x 10,872 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
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
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
#!/usr/bin/env ruby
#
# This sample code is a port of clutter-gst/examples/video-player.c. The
# image files used in this sample code are copied from clutter-gst/examples.
# They are licensed under the terms of the GNU Lesser General Public
# License, version 2.1 or (at your option) later.
#
# The original header:
#   video-player.c - A simple video player with an OSD.
#
#   Copyright (C) 2007,2008 OpenedHand
#   Copyright (C) 2013 Collabora
#
# Copyright (C) 2013-2014  Ruby-GNOME2 Project Team
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

require "optparse"

require "clutter-gst"

ClutterGst.load_version = "2.0"

SEEK_H = 14
SEEK_W = 440

GST_PLAY_FLAG_VIS = (1 << 3)

class VideoApp
  attr_accessor :stage
  attr_accessor :vtexture
  attr_accessor :control, :control_bg, :control_label
  attr_accessor :control_play, :control_pause
  attr_accessor :control_seek1, :control_seek2, :control_seekbar
  attr_accessor :controls_showing, :paused, :mouse_in_window
  attr_accessor :controls_timeout
  def initialize
    @controls_showing = false
    @paused = false
    @mouse_in_window = false
    @controls_timeout = 0
  end
end

opt_fullscreen = false
opt_loop = false

parser = OptionParser.new
parser.on("-f", "--[no-]fullscreen",
          "Start the player in fullscreen",
          "(#{opt_fullscreen})") do |boolean|
  opt_fullscreen = boolean
end
parser.on("-l", "--[no-]loop",
          "Start the video again once reached EOS",
          "(#{opt_loop})") do |boolean|
  opt_loop = boolean
end
parser.parse!

def controls_timeout_cb(app)
  app.controls_timeout = 0
  show_controls(app, false)
  false
end

def actor_animate(actor, mode, duration, first_property, *args)
  actor.save_easing_state
  actor.easing_mode = mode
  actor.easing_duration = duration

  actor.set_property(first_property, args.first)
end

def show_controls(app, vis)
  return if app.control.nil?

  if vis == true && app.controls_showing == true
    if app.controls_timeout == 0
      app.controls_timeout = GLib::Timeout.add_seconds(5) do
                               controls_timeout_cb(app)
                             end
    end
    return
  end

  if vis == true && app.controls_showing == false
    app.controls_showing = true
    app.stage.show_cursor
    actor_animate(app.control, :ease_out_quint, 250, "opacity", 224)
    return
  end

  if vis == false && app.controls_showing == true
    app.controls_showing = false
    if app.mouse_in_window
      app.stage.hide_cursor
    end
    actor_animate(app.control, :ease_out_quint, 250, "opacity", 0)
    return
  end
end

def toggle_pause_state(app)
  return if app.vtexture.nil?

  if app.paused
    app.vtexture.playing = true
    app.paused = false
    app.control_play.hide
    app.control_pause.show
  else
    app.vtexture.playing = false
    app.paused = true
    app.control_pause.hide
    app.control_play.show
  end
end

def position_controls(app, controls)
  stage_width, stage_height = app.stage.size
  bg_width, bg_height = app.control.size

  x = ((stage_width - bg_width) / 2).floor
  y =  stage_height - bg_height - 28

  controls.set_position(x, y)
end

def new_rectangle_with_color(color)
  actor = Clutter::Actor.new
  actor.background_color = color
  actor
end

stage_color = Clutter::Color.new(0, 0, 0, 0)
control_color1 = Clutter::Color.new(73, 74, 77, 0xee)
control_color2 = Clutter::Color.new(0xcc, 0xcc, 0xcc, 0xff)

if ARGV.length < 1
  puts "Usage: #{$0} [OPTIONS] <video file> - A simple video player"
  exit(false)
end

stage = Clutter::Stage.new
stage.background_color = Clutter::Color.new(0, 0, 0, 0)
stage.set_size(768, 576)
stage.set_minimum_size(640, 480)
stage.fullscreen = true if opt_fullscreen

app = VideoApp.new
app.stage = stage
app.vtexture = ClutterGst::VideoTexture.new

raise "failed to create vtexture" if app.vtexture.nil?

# By default ClutterGst seeks to the nearest key frame (faster). However
# it has the weird effect that when you click on the progress bar, the fill
# goes to the key frame position that can be quite far from where you
# clicked. Using the ACCURATE flag tells playbin2 to seek to the actual
# frame
app.vtexture.seek_flags = :accurate

app.vtexture.signal_connect("eos") do |media, app|
  if opt_loop
    media.progress = 0.0
    media.playing = true
  end
end

stage.signal_connect("allocation-changed") do |stage, box, flags|
  position_controls(app, app.control)
  show_controls(app, true)
end

stage.signal_connect("destroy") do
  Clutter.main_quit
end

# Handle it ourselves so can scale up for fullscreen better
app.vtexture.signal_connect_after("size-change") do |texture, base_width, base_height|
  stage_width, stage_height = stage.size

  # base_width and base_height are the actual dimensions of the buffers before
  # taking the pixel aspect ratio into account. We need to get the actual
  # size of the texture to display
  frame_width, frame_height = texture.size

  new_height = (frame_height * stage_width) / frame_width
  if new_height <= stage_height
    new_width = stage_width
    new_x = 0
    new_y = (stage_height - new_height) / 2
  else
    new_width = (frame_width * stage_height) / frame_height
    new_height = stage_height
    new_x = (stage_width - new_width) / 2
    new_y = 0
  end
  texture.set_position(new_x, new_y)
  texture.set_size(new_width, new_height)
end

# Load up out video texture
app.vtexture.filename = ARGV[0]

# Set up things so that a visualisation is played if there's no video
pipe = app.vtexture.pipeline
raise "Unable to get gstreamer pipeline!" unless pipe

iter = pipe.iterate_sinks
raise "Unable to iterate over sinks!" unless iter

playsink = nil
while (value = iter.next)[0] == :ok
  playsink = value[1].value
  sink_name = playsink.name  # unused
end

goomsource = Gst::ElementFactory.make("goom", "source")
raise "Unable to create goom visualiser!" unless goomsource

# TODO: warnings occurred
#playsink_flags = playsink.flags.value
#playsink_flags |= GST_PLAY_FLAG_VIS
playsink.vis_plugin = goomsource
#playsink.flags = playsink_flags

# Create the control UI
app.control = Clutter::Actor.new

app.control_bg = Clutter::Texture.new
app.control_bg.from_file = File.expand_path("vid-panel.png", File.dirname(__FILE__))
app.control_play = Clutter::Texture.new
app.control_play.from_file = File.expand_path("media-actions-start.png", File.dirname(__FILE__))
app.control_pause = Clutter::Texture.new
app.control_pause.from_file = File.expand_path("media-actions-pause.png", File.dirname(__FILE__))

app.control_seek1 = new_rectangle_with_color(control_color1)
app.control_seek2 = new_rectangle_with_color(control_color2)
app.control_seekbar = new_rectangle_with_color(control_color1)
app.control_seekbar.opacity = 0x99

app.control_label = Clutter::Text.new
app.control_label.font_name = "Sans Bold 14"
app.control_label.text = File.basename(ARGV[0])
stage.title = File.basename(ARGV[0])  # substitutes for label text
app.control_label.color = control_color1

app.control_play.hide

app.control.add_child(app.control_bg)
app.control.add_child(app.control_play)
app.control.add_child(app.control_pause)
app.control.add_child(app.control_seek1)
app.control.add_child(app.control_seek2)
app.control.add_child(app.control_seekbar)
app.control.add_child(app.control_label)

app.control.opacity = 0xee

app.control_play.set_position(22, 31)
app.control_pause.set_position(18, 31)

app.control_seek1.set_size(SEEK_W + 4, SEEK_H + 4)
app.control_seek1.set_position(80, 57)
app.control_seek2.set_size(SEEK_W, SEEK_H)
app.control_seek2.set_position(82, 59)
app.control_seekbar.set_size(0, SEEK_H)
app.control_seekbar.set_position(82, 59)

app.control_label.set_position(82, 29)

# Add control UI to stage
stage.add_child(app.vtexture)
stage.add_child(app.control)

position_controls(app, app.control)

stage.hide_cursor
actor_animate(app.control, :ease_out_quint, 1000, "opacity", 0)

# Hook up other events
stage.signal_connect("event") do |stage, event|
  handled = false

  case event.type
  when Clutter::EventType::MOTION
    show_controls(app, true)
    handled = true
  when Clutter::EventType::BUTTON_PRESS
    if app.controls_showing
      actor = stage.get_actor_at_pos(:all, event.x, event.y)
      if actor == app.control_pause || actor == app.control_play
        toggle_pause_state(app)
      elsif actor == app.control_seek1 ||
            actor == app.control_seek2 ||
            actor == app.control_seekbar
        x, y = app.control_seekbar.transformed_position
        dist = event.x - x
        def clamp(x, low, high)
          if x > high
            high
          elsif x < low
            low
          else
            x
          end
        end
        dist = clamp(dist, 0, SEEK_W)
        progress = 1.0 * dist / SEEK_W
        app.vtexture.progress == progress
      end
    end
    handled = true
  when Clutter::EventType::KEY_PRESS
    animation = nil
    case event.key_symbol
    when Clutter::Keys::KEY_d
      if app.vtexture
        app.stage.remove_child(app.vtexture)
        app.vtexture = nil
      end
      if app.control
        app.stage.remove_child(app.control)
        app.control = nil
      end
    when Clutter::Keys::KEY_q, Clutter::Keys::KEY_Escape
      app.stage.destroy
    when Clutter::Keys::KEY_e
      if app.vtexture
        app.vtexture .set_pivot_point(0.5, 0)
        animation = actor_animate(app.vtexture,
                                  :linear, 500,
                                  "rotation-angle-y", 360.0)
        animation.signal_connect_after("transitions-completed") do
          if app.vtexture
            app.vtexture.set_rotation_angle(:y_axis, 0.0)
          end
        end
        handled = true
      end
    else
      toggle_pause_state(app)
      handled = true
    end
  when Clutter::EventType::ENTER
    app.mouse_in_window = true
    app.stage.cursor_visible = app.controls_showing
  when Clutter::EventType::LEAVE
    app.mouse_in_window = false
    app.stage.show_cursor
  end
  handled
end

app.vtexture.signal_connect("notify::progress") do |video_texture, pspec|
  progress = video_texture.progress
  app.control_seekbar.set_size(progress * SEEK_W, SEEK_H)
end

app.vtexture.playing = true

stage.show

Clutter.main