~vikoadi/audience/move-subtitle-font-to-playerpage

« back to all changes in this revision

Viewing changes to src/Widgets/PlayerPage.vala

  • Committer: RabbitBot
  • Author(s): Viko Adi Rahmawan
  • Date: 2015-10-02 14:59:38 UTC
  • mfrom: (521.2.53 audience-refactor)
  • Revision ID: rabbitbot-20151002145938-yes2j7s5rmnwkzbs
- Significant code refactor
- Split Audience.vala into PlayerPage.vala and WelcomePage.vala, make file opening more predictable codewise
- Calculate video ratio properly using Gst.Video.calculate_display_ratio

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
namespace Audience {
 
2
    private  const string[] SUBTITLE_EXTENSIONS = {
 
3
        "sub",
 
4
        "srt",
 
5
        "smi",
 
6
        "ssa",
 
7
        "ass",
 
8
        "asc"
 
9
    };
 
10
    public class PlayerPage : Gtk.Bin {
 
11
        public GtkClutter.Embed           clutter;
 
12
        public Audience.Widgets.VideoPlayer video_player;
 
13
        private Audience.Widgets.BottomBar bottom_bar;
 
14
        private Clutter.Stage stage;
 
15
        private Gtk.Revealer unfullscreen_bar;
 
16
        private GtkClutter.Actor unfullscreen_actor;
 
17
        private GtkClutter.Actor bottom_actor;
 
18
        private GnomeMediaKeys             mediakeys;
 
19
 
 
20
        private bool mouse_primary_down = false;
 
21
        private bool fullscreened = false;
 
22
 
 
23
        public bool repeat {
 
24
            get{
 
25
                return bottom_bar.get_repeat ();
 
26
            }
 
27
 
 
28
            set{
 
29
                bottom_bar.set_repeat (value);
 
30
            }
 
31
        }
 
32
 
 
33
        private int bottom_bar_size = 0;
 
34
 
 
35
        public signal void ended ();
 
36
 
 
37
        public PlayerPage () {
 
38
            video_player = new Widgets.VideoPlayer();
 
39
            video_player.notify["playing"].connect (() => {bottom_bar.toggle_play_pause ();});
 
40
 
 
41
            clutter = new GtkClutter.Embed ();
 
42
            stage = (Clutter.Stage)clutter.get_stage ();
 
43
            stage.background_color = {0, 0, 0, 0};
 
44
            stage.use_alpha = true;
 
45
 
 
46
            video_player.add_constraint (new Clutter.BindConstraint (stage, Clutter.BindCoordinate.WIDTH, 0));
 
47
            video_player.add_constraint (new Clutter.BindConstraint (stage, Clutter.BindCoordinate.HEIGHT, 0));
 
48
 
 
49
            stage.add_child (video_player);
 
50
 
 
51
            bottom_bar = new Widgets.BottomBar (video_player);
 
52
            bottom_bar.set_valign (Gtk.Align.END);
 
53
            bottom_bar.play_toggled.connect (() => { video_player.playing = !video_player.playing; });
 
54
            bottom_bar.seeked.connect ((val) => { video_player.progress = val; });
 
55
            bottom_bar.unfullscreen.connect (()=>{set_fullscreen (false);});
 
56
            bottom_bar.set_repeat (false);
 
57
 
 
58
            unfullscreen_bar = bottom_bar.get_unfullscreen_button ();
 
59
 
 
60
            bottom_actor = new GtkClutter.Actor.with_contents (bottom_bar);
 
61
            bottom_actor.opacity = GLOBAL_OPACITY;
 
62
            stage.add_child (bottom_actor);
 
63
 
 
64
            unfullscreen_actor = new GtkClutter.Actor.with_contents (unfullscreen_bar);
 
65
            unfullscreen_actor.opacity = GLOBAL_OPACITY;
 
66
            stage.add_child (unfullscreen_actor);
 
67
 
 
68
            this.size_allocate.connect (on_size_allocate);
 
69
            App.get_instance ().mainwindow.key_press_event.connect (on_key_press_event);
 
70
            App.get_instance ().mainwindow.window_state_event.connect (on_window_state_event);
 
71
            if (App.get_instance ().mainwindow.is_maximized)
 
72
                set_fullscreen (true);
 
73
 
 
74
            //media keys
 
75
            try {
 
76
                mediakeys = Bus.get_proxy_sync (BusType.SESSION,
 
77
                    "org.gnome.SettingsDaemon", "/org/gnome/SettingsDaemon/MediaKeys");
 
78
                mediakeys.MediaPlayerKeyPressed.connect ((bus, app, key) => {
 
79
                    if (app != "audience")
 
80
                       return;
 
81
                    switch (key) {
 
82
                        case "Previous":
 
83
                            get_playlist_widget ().previous ();
 
84
                            break;
 
85
                        case "Next":
 
86
                            get_playlist_widget ().next ();
 
87
                            break;
 
88
                        case "Play":
 
89
                            video_player.playing = !video_player.playing;
 
90
                            break;
 
91
                        default:
 
92
                            break;
 
93
                    }
 
94
                });
 
95
 
 
96
                mediakeys.GrabMediaPlayerKeys("audience", 0);
 
97
            } catch (Error e) {
 
98
                warning (e.message);
 
99
            }
 
100
 
 
101
            App.get_instance ().mainwindow.motion_notify_event.connect ((event) => {
 
102
                if (mouse_primary_down && settings.move_window) {
 
103
                    mouse_primary_down = false;
 
104
                    App.get_instance ().mainwindow.begin_move_drag (Gdk.BUTTON_PRIMARY,
 
105
                        (int)event.x_root, (int)event.y_root, event.time);
 
106
                }
 
107
 
 
108
                Gtk.Allocation allocation;
 
109
                clutter.get_allocation (out allocation);
 
110
                return update_pointer_position (event.y, allocation.height);
 
111
            });
 
112
 
 
113
            this.button_press_event.connect ((event) => {
 
114
                if (event.button == Gdk.BUTTON_PRIMARY
 
115
                    && event.type == Gdk.EventType.2BUTTON_PRESS) // double left click
 
116
                    set_fullscreen(!fullscreened);
 
117
 
 
118
                if (event.button == Gdk.BUTTON_SECONDARY) // right click
 
119
                    bottom_bar.play_toggled ();
 
120
 
 
121
                if (event.button == Gdk.BUTTON_PRIMARY)
 
122
                    mouse_primary_down = true;
 
123
 
 
124
                return false;
 
125
            });
 
126
 
 
127
            this.button_release_event.connect ((event) => {
 
128
                if (event.button == Gdk.BUTTON_PRIMARY)
 
129
                    mouse_primary_down = false;
 
130
 
 
131
                return false;
 
132
            });
 
133
 
 
134
            this.leave_notify_event.connect ((event) => {
 
135
                Gtk.Allocation allocation;
 
136
                clutter.get_allocation (out allocation);
 
137
 
 
138
                if (event.x == event.window.get_width ())
 
139
                    return update_pointer_position (event.window.get_height (), allocation.height);
 
140
                else if (event.x == 0)
 
141
                    return update_pointer_position (event.window.get_height (), allocation.height);
 
142
 
 
143
                return update_pointer_position (event.y, allocation.height);
 
144
            });
 
145
 
 
146
            this.destroy.connect (() => {
 
147
                // FIXME:should find better way to decide if its end of playlist
 
148
                if (video_player.progress > 0.99)
 
149
                    settings.last_stopped = 0;
 
150
                else
 
151
                    settings.last_stopped = video_player.progress;
 
152
 
 
153
                get_playlist_widget ().save_playlist ();
 
154
            });
 
155
 
 
156
            /*events*/
 
157
            video_player.text_tags_changed.connect (bottom_bar.preferences_popover.setup_text);
 
158
            video_player.audio_tags_changed.connect (bottom_bar.preferences_popover.setup_audio);
 
159
            video_player.progression_changed.connect ((current_time, total_time) => {
 
160
                bottom_bar.set_progression_time (current_time, total_time);
 
161
            });
 
162
 
 
163
            //end
 
164
            video_player.ended.connect (() => {
 
165
                Idle.add (() => {
 
166
                    video_player.progress = 0;
 
167
                    if (!get_playlist_widget ().next ()) {
 
168
                        if (repeat) {
 
169
                            play_file (get_playlist_widget ().get_first_item ().get_uri ());
 
170
                            video_player.playing = true;
 
171
                        } else {
 
172
                            video_player.playing = false;
 
173
                            ended ();
 
174
                        }
 
175
                    }
 
176
                    return false;
 
177
                });
 
178
            });
 
179
 
 
180
            video_player.error.connect (() => {
 
181
                App.get_instance ().page = Page.WELCOME;
 
182
            });
 
183
 
 
184
            video_player.plugin_install_done.connect (() => {
 
185
                App.get_instance ().page = Page.PLAYER;
 
186
            });
 
187
 
 
188
            video_player.notify["playing"].connect (() => {
 
189
                App.get_instance ().mainwindow.set_keep_above (video_player.playing && settings.stay_on_top);
 
190
            });
 
191
 
 
192
            bottom_bar.time_widget.slider_motion_event.connect ((event) => {
 
193
                int x, y;
 
194
                bottom_bar.translate_coordinates (App.get_instance ().mainwindow, (int)event.x, (int)event.y, out x, out y);
 
195
                Gtk.Allocation allocation;
 
196
                clutter.get_allocation (out allocation);
 
197
                update_pointer_position (y, allocation.height);
 
198
            });
 
199
 
 
200
            //playlist wants us to open a file
 
201
            get_playlist_widget ().play.connect ((file) => {
 
202
                this.play_file (file.get_uri ());
 
203
            });
 
204
 
 
205
            bottom_bar.notify["child-revealed"].connect (() => {
 
206
                if (bottom_bar.child_revealed == true) {
 
207
                    App.get_instance ().mainwindow.get_window ().set_cursor (null);
 
208
                } else {
 
209
                    App.get_instance ().mainwindow.get_window ().set_cursor (new Gdk.Cursor (Gdk.CursorType.BLANK_CURSOR));
 
210
                }
 
211
            });
 
212
 
 
213
            stage.notify["allocation"].connect (() => {allocate_bottombar ();});
 
214
 
 
215
            add (clutter);
 
216
 
 
217
            show_all ();
 
218
 
 
219
        }
 
220
 
 
221
        ~PlayerPage () {
 
222
            video_player.playing = false;
 
223
 
 
224
            App.get_instance ().set_content_size (0, 0, 0);
 
225
            this.size_allocate.disconnect (on_size_allocate);
 
226
            App.get_instance ().mainwindow.window_state_event.disconnect (on_window_state_event);
 
227
            App.get_instance ().mainwindow.key_press_event.disconnect (on_key_press_event);
 
228
            if (App.get_instance ().mainwindow.get_window () != null)
 
229
                App.get_instance ().mainwindow.get_window ().set_cursor (null);
 
230
 
 
231
            App.get_instance ().mainwindow.unfullscreen ();
 
232
            if (fullscreened)
 
233
                App.get_instance ().mainwindow.maximize ();
 
234
 
 
235
            video_player.text_tags_changed.disconnect (bottom_bar.preferences_popover.setup_text);
 
236
            video_player.audio_tags_changed.disconnect (bottom_bar.preferences_popover.setup_audio);
 
237
        }
 
238
 
 
239
        public void play_file (string uri, bool from_beginning = true) {
 
240
            debug ("Opening %s", uri);
 
241
            video_player.uri = uri;
 
242
            get_playlist_widget ().set_current (uri);
 
243
            bottom_bar.set_preview_uri (uri);
 
244
 
 
245
            string? sub_uri = get_subtitle_for_uri (uri);
 
246
            if (sub_uri != null)
 
247
                video_player.set_subtitle_uri (sub_uri);
 
248
 
 
249
            App.get_instance ().set_window_title (get_title (uri));
 
250
            video_player.relayout ();
 
251
 
 
252
            int target_width, target_height, center_x, center_y;
 
253
            get_target_size (out target_width, out target_height, out center_x, out center_y);
 
254
            get_window ().move_resize (center_x, center_y, target_width, target_height);
 
255
 
 
256
            update_aspect_ratio ();
 
257
            video_player.playing = !settings.playback_wait;
 
258
            if (from_beginning)
 
259
                video_player.progress = 0.0;
 
260
 
 
261
            Gtk.RecentManager recent_manager = Gtk.RecentManager.get_default ();
 
262
            recent_manager.add_item (uri);
 
263
 
 
264
            /*subtitles/audio tracks*/
 
265
            bottom_bar.preferences_popover.setup_text ();
 
266
            bottom_bar.preferences_popover.setup_audio ();
 
267
        }
 
268
 
 
269
        public void next () {
 
270
            get_playlist_widget ().next ();
 
271
        }
 
272
 
 
273
        public void prev () {
 
274
            get_playlist_widget ().next ();
 
275
        }
 
276
 
 
277
        public void resume_last_videos () {
 
278
            play_file (settings.current_video);
 
279
            video_player.playing = false;
 
280
            Idle.add (() => {
 
281
                    if (settings.resume_videos)
 
282
                        video_player.progress = settings.last_stopped;
 
283
                    else
 
284
                        video_player.progress = 0.0;
 
285
 
 
286
                    return false;
 
287
                    });
 
288
            video_player.playing = !settings.playback_wait;
 
289
        }
 
290
 
 
291
        public void append_to_playlist (File file) {
 
292
            get_playlist_widget ().add_item (file);
 
293
        }
 
294
 
 
295
        public void play_first_in_playlist () {
 
296
            var file = get_playlist_widget ().get_first_item ();
 
297
            play_file (file.get_uri ());
 
298
            video_player.progress = 0.0;
 
299
        }
 
300
 
 
301
        private Widgets.Playlist get_playlist_widget () {
 
302
            return bottom_bar.playlist_popover.playlist;
 
303
        }
 
304
 
 
305
        private string? get_subtitle_for_uri (string uri) {
 
306
            string without_ext;
 
307
            int last_dot = uri.last_index_of (".", 0);
 
308
            int last_slash = uri.last_index_of ("/", 0);
 
309
 
 
310
            if (last_dot < last_slash) //we dont have extension
 
311
                without_ext = uri;
 
312
            else
 
313
                without_ext = uri.slice (0, last_dot);
 
314
 
 
315
            foreach (string ext in SUBTITLE_EXTENSIONS){
 
316
                string sub_uri = without_ext + "." + ext;
 
317
                if (File.new_for_uri (sub_uri).query_exists ())
 
318
                    return sub_uri;
 
319
            }
 
320
            return null;
 
321
        }
 
322
 
 
323
        public static bool is_subtitle (string uri) {
 
324
            if (uri.length < 4 || uri.get_char (uri.length-4) != '.')
 
325
                return false;
 
326
 
 
327
            foreach (string ext in SUBTITLE_EXTENSIONS) {
 
328
                if (uri.down ().has_suffix (ext))
 
329
                    return true;
 
330
            }
 
331
 
 
332
            return false;
 
333
        }
 
334
 
 
335
        private void allocate_bottombar () {
 
336
            bottom_actor.width = stage.get_width ();
 
337
            bottom_bar.queue_resize ();
 
338
            bottom_actor.y = stage.get_height () - bottom_bar_size;
 
339
            unfullscreen_actor.y = 6;
 
340
            unfullscreen_actor.x = stage.get_width () - bottom_bar_size - 6;
 
341
        }
 
342
 
 
343
        public bool update_pointer_position (double y, int window_height) {
 
344
            allocate_bottombar ();
 
345
            App.get_instance ().mainwindow.get_window ().set_cursor (null);
 
346
            if (bottom_bar_size == 0) {
 
347
                int minimum = 0;
 
348
                bottom_bar.get_preferred_height (out minimum, out bottom_bar_size);
 
349
            }
 
350
 
 
351
            bottom_bar.reveal_control ();
 
352
 
 
353
            return false;
 
354
        }
 
355
 
 
356
        private void get_target_size (out int target_width, out int target_height,
 
357
                out int center_x, out int center_y) {
 
358
            Gdk.Rectangle monitor;
 
359
            var screen = Gdk.Screen.get_default ();
 
360
            screen.get_monitor_geometry (screen.get_monitor_at_window (get_window ()),
 
361
                    out monitor);
 
362
 
 
363
            if (monitor.width > video_player.video_width
 
364
                && monitor.height > video_player.video_height) {
 
365
                target_width = (int) video_player.video_width;
 
366
                target_height = (int) video_player.video_height;
 
367
            } else {
 
368
                target_width = (int)(monitor.width * 0.9);
 
369
                target_height = (int)((double) video_player.video_height / video_player.video_width * target_width);
 
370
            }
 
371
            center_x = monitor.width / 2 - target_width /2 + monitor.x;
 
372
            center_y = monitor.height / 2 - target_height /2 + monitor.y;
 
373
        }
 
374
 
 
375
        private bool on_key_press_event (Gdk.EventKey e) {
 
376
            switch (e.keyval) {
 
377
                case Gdk.Key.p:
 
378
                case Gdk.Key.space:
 
379
                    video_player.playing = !video_player.playing;
 
380
                    break;
 
381
                case Gdk.Key.Escape:
 
382
                    if (fullscreened) {
 
383
                        set_fullscreen (false);
 
384
                    } else {
 
385
                        App.get_instance ().mainwindow.destroy ();
 
386
                    }
 
387
                    return true;
 
388
                case Gdk.Key.Down:
 
389
                    if (modifier_is_pressed (e, Gdk.ModifierType.SHIFT_MASK)) {
 
390
                        video_player.seek_jump_seconds (-5); // 5 secs
 
391
                    } else {
 
392
                        video_player.seek_jump_seconds (-60); // 1 min
 
393
                    }
 
394
                    bottom_bar.reveal_control ();
 
395
                    break;
 
396
                case Gdk.Key.Left:
 
397
                    if (modifier_is_pressed (e, Gdk.ModifierType.SHIFT_MASK)) {
 
398
                        video_player.seek_jump_seconds (-1); // 1 sec
 
399
                    } else {
 
400
                        video_player.seek_jump_seconds (-10); // 10 secs
 
401
                    }
 
402
                    bottom_bar.reveal_control ();
 
403
                    break;
 
404
                case Gdk.Key.Right:
 
405
                    if (modifier_is_pressed (e, Gdk.ModifierType.SHIFT_MASK)) {
 
406
                        video_player.seek_jump_seconds (1); // 1 sec
 
407
                    } else {
 
408
                        video_player.seek_jump_seconds (10); // 10 secs
 
409
                    }
 
410
                    bottom_bar.reveal_control ();
 
411
                    break;
 
412
                case Gdk.Key.Up:
 
413
                    if (modifier_is_pressed (e, Gdk.ModifierType.SHIFT_MASK)) {
 
414
                        video_player.seek_jump_seconds (5); // 5 secs
 
415
                    } else {
 
416
                        video_player.seek_jump_seconds (60); // 1 min
 
417
                    }
 
418
                    bottom_bar.reveal_control ();
 
419
                    break;
 
420
                case Gdk.Key.Page_Down:
 
421
                    video_player.seek_jump_seconds (-600); // 10 mins
 
422
                    bottom_bar.reveal_control ();
 
423
                    break;
 
424
                case Gdk.Key.Page_Up:
 
425
                    video_player.seek_jump_seconds (600); // 10 mins
 
426
                    bottom_bar.reveal_control ();
 
427
                    break;
 
428
                case Gdk.Key.a:
 
429
                    bottom_bar.preferences_popover.next_audio ();
 
430
                    break;
 
431
                case Gdk.Key.s:
 
432
                    bottom_bar.preferences_popover.next_text ();
 
433
                    break;
 
434
                case Gdk.Key.f:
 
435
                    if (fullscreened)
 
436
                        set_fullscreen (false);
 
437
                    else
 
438
                        set_fullscreen (true);
 
439
 
 
440
                    break;
 
441
                default:
 
442
                    break;
 
443
            }
 
444
 
 
445
            return false;
 
446
        }
 
447
 
 
448
        private bool on_window_state_event (Gdk.EventWindowState e){
 
449
            switch (e.changed_mask){
 
450
                case Gdk.WindowState.FULLSCREEN:
 
451
                fullscreened= ((e.new_window_state & Gdk.WindowState.FULLSCREEN)!=0);
 
452
                break;
 
453
                case Gdk.WindowState.MAXIMIZED:
 
454
                bool currently_maximixed = ((e.new_window_state & Gdk.WindowState.MAXIMIZED)!=0);
 
455
                set_fullscreen (currently_maximixed);
 
456
                break;
 
457
            }
 
458
            return false;
 
459
        }
 
460
 
 
461
        private void set_fullscreen (bool full){
 
462
            fullscreened = full;
 
463
            if (full) {
 
464
                App.get_instance ().mainwindow.fullscreen ();
 
465
            } else {
 
466
                // unfullscreen shoulnd't be call from elsewhere other than here
 
467
                App.get_instance ().mainwindow.maximize ();
 
468
                App.get_instance ().mainwindow.unfullscreen ();
 
469
            }
 
470
            bottom_bar.fullscreen = full;
 
471
        }
 
472
 
 
473
        private uint update_aspect_ratio_timeout = 0;
 
474
        private bool update_aspect_ratio_locked = false;
 
475
        private int prev_width = 0;
 
476
        private int prev_height = 0;
 
477
        private int old_h = -1;
 
478
        private int old_w = -1;
 
479
        /**
 
480
         * Updates the window's aspect ratio locking if enabled.
 
481
         * Return type is just there to make it compatible with Idle.add()
 
482
         */
 
483
        private bool update_aspect_ratio () {
 
484
            if (!settings.keep_aspect
 
485
                || video_player.video_width < 1
 
486
                || video_player.height < 1
 
487
                || !clutter.visible)
 
488
                return false;
 
489
 
 
490
            if (update_aspect_ratio_timeout != 0)
 
491
                Source.remove (update_aspect_ratio_timeout);
 
492
 
 
493
            update_aspect_ratio_timeout = Timeout.add (200, () => {
 
494
                Gtk.Allocation a;
 
495
                clutter.get_allocation (out a);
 
496
                print ("%i %i %i,%i\n", a.x, a.y, (this.get_allocated_width () - this.clutter.get_allocated_width ()) / 2, (this.get_allocated_height () - this.clutter.get_allocated_height ()) / 2);
 
497
 
 
498
                double width, height;
 
499
                width = clutter.get_allocated_width ();
 
500
                height = width * video_player.video_height / (double) video_player.video_width;
 
501
 
 
502
                App.get_instance ().set_content_size (width, height,clutter.get_allocated_height ());
 
503
 
 
504
                prev_width = this.get_allocated_width ();
 
505
                prev_height = this.get_allocated_height ();
 
506
 
 
507
                update_aspect_ratio_timeout = 0;
 
508
 
 
509
                return false;
 
510
            });
 
511
 
 
512
            return false;
 
513
        }
 
514
        private void on_size_allocate (Gtk.Allocation alloc) {
 
515
            if (alloc.width != old_w || alloc.height != old_h) {
 
516
                if (video_player.relayout ()) {
 
517
                    old_w = alloc.width;
 
518
                    old_h = alloc.height;
 
519
                }
 
520
            }
 
521
 
 
522
            if (prev_width != this.get_allocated_width () || prev_height != this.get_allocated_height ())
 
523
                Idle.add (update_aspect_ratio);
 
524
        }
 
525
 
 
526
    }
 
527
}