1// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*-
2/*-
3 * Copyright (c) 2013-2014 Audience Developers (http://launchpad.net/pantheon-chat)
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * Authored by: Corentin Noël <corentin@elementaryos.org>
19 */
20
21public class Audience.Widgets.PreviewPopover : Gtk.Popover {
22    private enum PlayFlags {
23        VIDEO = (1 << 0),
24        AUDIO = (1 << 1),
25        TEXT = (1 << 2),
26        VIS = (1 << 3),
27        SOFT_VOLUME = (1 << 4),
28        NATIVE_AUDIO = (1 << 5),
29        NATIVE_VIDEO = (1 << 6),
30        DOWNLOAD = (1 << 7),
31        BUFFERING = (1 << 8),
32        DEINTERLACE = (1 << 9),
33        SOFT_COLORBALANCE = (1 << 10)
34    }
35
36    ClutterGst.Playback playback;
37    GtkClutter.Embed clutter;
38    uint loop_timer_id = 0;
39    uint show_timer_id = 0;
40    uint hide_timer_id = 0;
41    uint idle_id = 0;
42    double req_progress = -1;
43    bool req_loop = false;
44
45    public PreviewPopover (ClutterGst.Playback main_playback) {
46        opacity = GLOBAL_OPACITY;
47        can_focus = false;
48        sensitive = false;
49        modal = false;
50
51        playback = new ClutterGst.Playback ();
52        playback.ready.connect (() => {
53            unowned Gst.Element pipeline = playback.get_pipeline ();
54            int flags;
55            pipeline.get ("flags", out flags);
56            flags &= ~PlayFlags.TEXT;   //disable subtitle
57            flags &= ~PlayFlags.AUDIO;  //disable audio sink
58            pipeline.set ("flags", flags);
59        });
60
61        playback.set_seek_flags (ClutterGst.SeekFlags.ACCURATE);
62        playback.uri = main_playback.uri;
63        playback.playing = false;
64        clutter = new GtkClutter.Embed ();
65        clutter.margin = 3;
66        var stage = (Clutter.Stage)clutter.get_stage ();
67        stage.background_color = {0, 0, 0, 0};
68
69        var video_actor = new Clutter.Actor ();
70#if VALA_0_34
71        var aspect_ratio = new ClutterGst.Aspectratio ();
72#else
73        var aspect_ratio = ClutterGst.Aspectratio.@new ();
74#endif
75        ((ClutterGst.Aspectratio) aspect_ratio).paint_borders = false;
76        ((ClutterGst.Content) aspect_ratio).player = playback;
77        video_actor.content = aspect_ratio;
78        ((ClutterGst.Content) aspect_ratio).size_change.connect ((width, height) => {
79            if (width > 0 && height > 0) {
80                double diagonal = Math.sqrt ((width * width) + (height * height));
81                double k = 230 / diagonal; // for 16:9 ratio it produces width of ~200px
82                clutter.set_size_request ((int)(width * k), (int)(height * k));
83            }
84        });
85
86        video_actor.add_constraint (new Clutter.BindConstraint (stage, Clutter.BindCoordinate.WIDTH, 0));
87        video_actor.add_constraint (new Clutter.BindConstraint (stage, Clutter.BindCoordinate.HEIGHT, 0));
88
89        stage.add_child (video_actor);
90        add (clutter);
91
92        closed.connect (() => {
93            playback.playing = false;
94            cancel_loop_timer ();
95            cancel_timer (ref show_timer_id);
96            cancel_timer (ref hide_timer_id);
97        });
98
99        hide.connect (() => {
100            playback.playing = false;
101            cancel_loop_timer ();
102        });
103    }
104
105    ~PreviewPopover () {
106        playback.playing = false;
107        cancel_loop_timer ();
108    }
109
110    public void set_preview_progress (double progress, bool loop = false) {
111        req_progress = progress;
112        req_loop = loop;
113
114        if (!visible || idle_id > 0) {
115            return;
116        }
117
118        if (loop) {
119            cancel_loop_timer ();
120        }
121
122        idle_id = Idle.add_full (GLib.Priority.LOW, () => {
123            playback.playing = true;
124            playback.progress = progress;
125            playback.playing = loop;
126            if (loop) {
127                loop_timer_id = Timeout.add_seconds (5, () => {
128                    set_preview_progress (progress, true);
129                    loop_timer_id = 0;
130                    return false;
131                });
132            }
133            idle_id = 0;
134            return false;
135        });
136    }
137
138    public void update_pointing (int x) {
139        var pointing = pointing_to;
140        pointing.x = x;
141
142        // changing the width properly updates arrow position when popover hits the edge
143        if (pointing.width == 0) {
144            pointing.width = 2;
145            pointing.x -= 1;
146        } else {
147            pointing.width = 0;
148        }
149
150        set_pointing_to (pointing);
151    }
152
153    public void realign_pointing (int parent_width) {
154        if (visible) {
155            update_pointing ((int)(req_progress * parent_width));
156        }
157    }
158
159    public void schedule_show () {
160        if (show_timer_id > 0) {
161            return;
162        }
163        cancel_timer (ref hide_timer_id);
164
165        show_timer_id = Timeout.add (300, () => {
166            show_all ();
167            if (req_progress >= 0) {
168                set_preview_progress (req_progress, req_loop);
169            }
170            show_timer_id = 0;
171            return false;
172        });
173    }
174
175    public void schedule_hide () {
176        if (hide_timer_id > 0) {
177            return;
178        }
179        cancel_timer (ref show_timer_id);
180
181        hide_timer_id = Timeout.add (300, () => {
182            hide ();
183            hide_timer_id = 0;
184            return false;
185        });
186    }
187
188    private void cancel_loop_timer () {
189        cancel_timer (ref loop_timer_id);
190    }
191
192    private void cancel_timer (ref uint timer_id) {
193        if (timer_id > 0) {
194            Source.remove (timer_id);
195            timer_id = 0;
196        }
197    }
198}
199