Created
July 21, 2010 19:50
-
-
Save eagsalazar/485014 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Clutter; | |
using Gst; | |
using ClutterGst; | |
using Mx; | |
public class VideoPlayer { | |
private string fileName; | |
private Pipeline pipeline; | |
private Element src; | |
private Element sink; | |
private Element decode; | |
private Element colorspace; | |
public VideoPlayer(string? _fileName, Element _sink) { | |
fileName = _fileName; | |
sink = _sink; | |
setup_pipeline(); | |
} | |
public void play() { | |
if(pipeline.current_state == State.PLAYING) | |
pipeline.set_state(State.PAUSED); | |
else | |
pipeline.set_state(State.PLAYING); | |
} | |
public void stop() { | |
pipeline.set_state(State.READY); | |
} | |
private void setup_pipeline() { | |
pipeline = new Pipeline("pipeline"); | |
// To play a video file you need these elements: | |
// - "filesrc" to read file or "v4l2src" (src) | |
// - "decodebin" to decode file (filter) | |
// - "ffmpegcolorspace" to do colorspace conversion (filter) | |
// - Some sink that we have passed in. | |
// If we don't have a fileName, then use the video camera, which is fun. | |
if(fileName == null) { | |
src = ElementFactory.make("v4l2src", null); | |
} else { | |
src = ElementFactory.make("filesrc", null); | |
// Unlike in GOBject where we use .set_attribute, here we use .set as this is | |
// built into vala, I guess. | |
src.set("location", fileName); | |
} | |
decode = ElementFactory.make("decodebin", null); | |
colorspace = ElementFactory.make("ffmpegcolorspace", null); | |
// decodebins output (src) isnt created until after the state != State.READY. So we wait | |
// for the new-decoded-pad event and hook up those pads at that time. | |
// | |
// The last argument is because on_new_decoded_pad is static so we need to | |
// provide a reference to the instance variables it will need. | |
// | |
// We should be able to simply do: | |
// | |
// decode.new_decoded_pad.connect(on_new_decoded_pad); | |
// | |
// Where on_new_decoded_pad would be an instance method. However | |
// There is a bug that requires we do this crap. See on_new_decoded_pad comments. | |
Signal.connect(decode, "new-decoded-pad", (GLib.Callback) on_new_decoded_pad, colorspace); | |
// Add all our elements to the pipeline | |
pipeline.add_many(src, decode, colorspace, sink); | |
// Hook up the elements that we can hook up. Which pads to hook up is | |
// resolved automatically by direction and type when not ambiguous. | |
src.link(decode); | |
colorspace.link(sink); | |
} | |
// The last argument inherits from GPointer in C which in the context means any reference type. | |
// We declare this method to be static and use the last arg to get a ref back to the instance, | |
// instead of just making this an instance method because of bug: (which I don't totally understand) | |
// https://bugzilla.gnome.org/show_bug.cgi?id=615979 | |
private static void on_new_decoded_pad(Element decodebin, Pad pad, bool last, Element colorspace) { | |
pad.link(colorspace.get_pad("sink")); | |
} | |
} | |
// Clutter.Texture is an actor, for drawing images on. We are going | |
// to draw video frames on this texture. It exposes a public attribute | |
// player which is an instance of VideoPlayer. | |
class VideoTexture : Clutter.Texture { | |
public VideoPlayer player; | |
private ClutterGst.VideoSink sink; | |
// Input args | |
private string videoFile; | |
public VideoTexture(string? _videoFile) { | |
videoFile = _videoFile; | |
// ClutterGst.VideoSink is a Gst sink element. We pass in this | |
// actor so that this sink knows to copy frames to that texture. | |
sink = new ClutterGst.VideoSink(this); | |
// Create the player | |
player = new VideoPlayer(videoFile, sink); | |
} | |
} | |
class ClutterGstTest { | |
private Timeline spin_timeline; | |
private Timeline fade_out_timeline; | |
private Timeline fade_in_timeline; | |
private Timeline scale_up_timeline; | |
private Timeline scale_down_timeline; | |
private Alpha spin_alpha; | |
private BehaviourRotate behaviour_rotate; | |
private Alpha zoom_alpha_down; | |
private Alpha zoom_alpha_up; | |
private BehaviourScale behaviour_scale_down; | |
private BehaviourScale behaviour_scale_up; | |
private Alpha fade_alpha_out; | |
private Alpha fade_alpha_in; | |
private BehaviourOpacity behaviour_fade_out; | |
private BehaviourOpacity behaviour_fade_in; | |
public VideoTexture videoTexture; | |
private bool on_button_click(ButtonEvent event) { | |
spin(); | |
return true; | |
} | |
public void spin() { | |
spin_timeline.start(); | |
} | |
public void zoom() { | |
scale_down_timeline.start(); | |
} | |
public void fade() { | |
fade_out_timeline.start(); | |
} | |
public void play() { | |
// Play must be called after the stage has been shown or else segfault!! | |
// Calling play sets the gst pipelines state to State.PLAYING | |
videoTexture.player.play(); | |
} | |
public ClutterGstTest(string? vid, Stage stage, float scale, int loopTime, RotateAxis axis = RotateAxis.X_AXIS) { | |
videoTexture = new VideoTexture(vid); | |
videoTexture.height = scale*stage.height; | |
videoTexture.width = scale*stage.width; | |
videoTexture.anchor_gravity = Gravity.CENTER; | |
videoTexture.y = stage.height/2; | |
videoTexture.x = stage.width/2; | |
videoTexture.button_press_event.connect(on_button_click); | |
videoTexture.reactive = true; | |
// spin | |
spin_timeline = new Timeline(loopTime); | |
spin_alpha = new Alpha.full(spin_timeline, AnimationMode.EASE_IN_OUT_QUAD); | |
behaviour_rotate = new BehaviourRotate(spin_alpha, axis, RotateDirection.CW, 0, 360); | |
behaviour_rotate.apply(videoTexture); | |
// zoom | |
scale_down_timeline = new Timeline(1000); | |
zoom_alpha_down = new Alpha.full(scale_down_timeline, AnimationMode.EASE_OUT_BOUNCE); | |
behaviour_scale_down = new BehaviourScale(zoom_alpha_down, 1, 1, 0.2, 0.2); | |
behaviour_scale_down.apply(videoTexture); | |
scale_up_timeline = new Timeline(1000); | |
zoom_alpha_up = new Alpha.full(scale_up_timeline, AnimationMode.EASE_OUT_BOUNCE); | |
behaviour_scale_up = new BehaviourScale(zoom_alpha_up, 0.2, 0.2, 1, 1); | |
behaviour_scale_up.apply(videoTexture); | |
scale_down_timeline.completed.connect(() => { | |
scale_up_timeline.start(); | |
}); | |
// fade | |
fade_out_timeline = new Timeline(1000); | |
fade_alpha_out = new Alpha.full(fade_out_timeline, AnimationMode.LINEAR); | |
behaviour_fade_out = new BehaviourOpacity(fade_alpha_out, 255, 50); | |
behaviour_fade_out.apply(videoTexture); | |
fade_in_timeline = new Timeline(1000); | |
fade_alpha_in = new Alpha.full(fade_in_timeline, AnimationMode.LINEAR); | |
behaviour_fade_in = new BehaviourOpacity(fade_alpha_in, 50, 255); | |
behaviour_fade_in.apply(videoTexture); | |
fade_out_timeline.completed.connect(() => { | |
stdout.printf("starting fade in.\n"); | |
fade_in_timeline.start(); | |
}); | |
stage.add_actor(videoTexture); | |
} | |
} | |
class ButtonBar : Mx.BoxLayout { | |
public signal void play(); | |
public signal void spin1(); | |
public signal void spin2(); | |
public signal void zoom1(); | |
public signal void zoom2(); | |
public signal void fade1(); | |
public signal void fade2(); | |
public ButtonBar() { | |
reactive = true; | |
Mx.Button button; | |
button = new Mx.Button.with_label("Play/Pause"); | |
button.clicked.connect(() => { play(); }); | |
add((Clutter.Actor)button); | |
button = new Mx.Button.with_label("Spin 1"); | |
button.clicked.connect(() => { spin1(); }); | |
add((Clutter.Actor)button); | |
button = new Mx.Button.with_label("Spin 2"); | |
button.clicked.connect(() => { spin2(); }); | |
add((Clutter.Actor)button); | |
button = new Mx.Button.with_label("Zoom 1"); | |
button.clicked.connect(() => { zoom1(); }); | |
add((Clutter.Actor)button); | |
button = new Mx.Button.with_label("Zoom 2"); | |
button.clicked.connect(() => { zoom2(); }); | |
add((Clutter.Actor)button); | |
button = new Mx.Button.with_label("Fade 1"); | |
button.clicked.connect(() => { fade1(); }); | |
add((Clutter.Actor)button); | |
button = new Mx.Button.with_label("Fade 2"); | |
button.clicked.connect(() => { fade2(); }); | |
add((Clutter.Actor)button); | |
} | |
// This code is broken until this bug is fixed: | |
// https://bugzilla.gnome.org/show_bug.cgi?id=624902 | |
/* | |
delegate void ButtonAction(Button button); | |
private void create_button(string tooltip, string label, owned ButtonAction callback) { | |
var button = new Mx.Button.with_label(label); | |
button.tooltip_text = tooltip; | |
button.clicked.connect(callback); | |
add((Clutter.Actor)button); | |
} | |
public ButtonBar() { | |
create_button("Click to play/pause", "Play", (() => { play(); })); | |
create_button("Click to spin 1", "Spin 1", (() => { spin1(); })); | |
create_button("Click to spin 2", "Spin 2", (() => { spin2(); })); | |
} | |
*/ | |
} | |
bool on_keypress_event(KeyEvent e) { | |
Clutter.main_quit(); | |
return true; | |
} | |
void main(string[] args) { | |
var video_file1 = null; | |
var d = ""; // FIXME - get cwd | |
var video_file2 = @"$(d)../data/Rendezvous.avi"; | |
// Initialize both Clutter and ClutterGst | |
Clutter.init(ref args); | |
Gst.init(ref args); | |
ClutterGst.init(ref args); | |
// Each window by default has a stage associated with it | |
var stage = Stage.get_default(); | |
stage.color = Color.from_string("black"); | |
stage.title = "ClutterGstTest"; | |
stage.width = 800; | |
stage.height = 600; | |
var test1 = new ClutterGstTest(video_file1, stage, 0.9f, 1400, RotateAxis.Y_AXIS); | |
var test2 = new ClutterGstTest(video_file2, stage, 0.2f, 400, RotateAxis.X_AXIS); | |
test2.videoTexture.x = stage.width - (test2.videoTexture.width/2 + 50f); | |
test2.videoTexture.y = stage.height - (test2.videoTexture.height/2 + 50f); | |
test1.play(); | |
test2.play(); | |
var buttonBar = new ButtonBar(); | |
stage.add_actor(buttonBar); | |
buttonBar.anchor_gravity = Gravity.SOUTH; | |
float barIdlePos = stage.height + 50f; | |
buttonBar.y = barIdlePos; | |
buttonBar.x = stage.width/2; | |
buttonBar.play.connect(() => { | |
test1.play(); | |
test2.play(); | |
}); | |
buttonBar.spin1.connect(() => { test1.spin(); }); | |
buttonBar.spin2.connect(() => { test2.spin(); }); | |
buttonBar.zoom1.connect(() => { test1.zoom(); }); | |
buttonBar.zoom2.connect(() => { test2.zoom(); }); | |
buttonBar.fade1.connect(() => { test1.fade(); }); | |
buttonBar.fade2.connect(() => { test2.fade(); }); | |
buttonBar.enter_event.connect(() => { | |
buttonBar.animate(AnimationMode.LINEAR, 200, "y", stage.height); | |
return true; | |
}); | |
buttonBar.leave_event.connect(() => { | |
buttonBar.animate(AnimationMode.LINEAR, 200, "y", barIdlePos); | |
return true; | |
}); | |
unowned Style style = Style.get_default(); | |
try { | |
style.load_from_file("clutter-gst-test.css"); | |
} catch (GLib.Error e) { | |
stdout.printf("Error in style.load_from_file: %s\n", e.message); | |
} | |
buttonBar.set_style(style); | |
buttonBar.spacing = 10; | |
stage.show_all(); | |
Clutter.main(); | |
stage.hide.connect(Clutter.main_quit); | |
stage.key_press_event.connect(on_keypress_event); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment