summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavi Artigas <xartigas@fluendo.com>2013-05-17 11:46:48 +0200
committerXavi Artigas <xartigas@fluendo.com>2013-05-17 11:46:48 +0200
commit76f7e541ec56739d918b056e9f6b7503ab86ba5d (patch)
treeb81848cdb3d52e568873d375d56fc7e4a716e7a9
parent079ad1bf151a8578b81ac1f916e206ef37f286e4 (diff)
Enable seeking by dragging the time slider. Add a bunch of online clips to the hardcoded library.
-rw-r--r--gst-sdk/tutorials/xcode iOS/Tutorial 4/GStreamerBackend.h3
-rw-r--r--gst-sdk/tutorials/xcode iOS/Tutorial 4/GStreamerBackend.m92
-rw-r--r--gst-sdk/tutorials/xcode iOS/Tutorial 4/LibraryViewController.m29
-rw-r--r--gst-sdk/tutorials/xcode iOS/Tutorial 4/VideoViewController.h3
-rw-r--r--gst-sdk/tutorials/xcode iOS/Tutorial 4/VideoViewController.m35
-rw-r--r--gst-sdk/tutorials/xcode iOS/Tutorial 4/en.lproj/MainStoryboard_iPad.storyboard7
6 files changed, 152 insertions, 17 deletions
diff --git a/gst-sdk/tutorials/xcode iOS/Tutorial 4/GStreamerBackend.h b/gst-sdk/tutorials/xcode iOS/Tutorial 4/GStreamerBackend.h
index 1fe4853..f16ea70 100644
--- a/gst-sdk/tutorials/xcode iOS/Tutorial 4/GStreamerBackend.h
+++ b/gst-sdk/tutorials/xcode iOS/Tutorial 4/GStreamerBackend.h
@@ -22,4 +22,7 @@
/* Set the URI to be played */
-(void) setUri:(NSString*)uri;
+/* Set the position to seek to, in milliseconds */
+-(void) setPosition:(NSInteger)milliseconds;
+
@end \ No newline at end of file
diff --git a/gst-sdk/tutorials/xcode iOS/Tutorial 4/GStreamerBackend.m b/gst-sdk/tutorials/xcode iOS/Tutorial 4/GStreamerBackend.m
index f789edc..b4db2d9 100644
--- a/gst-sdk/tutorials/xcode iOS/Tutorial 4/GStreamerBackend.m
+++ b/gst-sdk/tutorials/xcode iOS/Tutorial 4/GStreamerBackend.m
@@ -7,6 +7,10 @@
GST_DEBUG_CATEGORY_STATIC (debug_category);
#define GST_CAT_DEFAULT debug_category
+/* Do not allow seeks to be performed closer than this distance. It is visually useless, and will probably
+ * confuse some demuxers. */
+#define SEEK_MIN_DELAY (500 * GST_MSECOND)
+
@interface GStreamerBackend()
-(void)setUIMessage:(gchar*) message;
-(void)app_function;
@@ -14,15 +18,17 @@ GST_DEBUG_CATEGORY_STATIC (debug_category);
@end
@implementation GStreamerBackend {
- id ui_delegate; /* Class that we use to interact with the user interface */
- GstElement *pipeline; /* The running pipeline */
- GstElement *video_sink;/* The video sink element which receives XOverlay commands */
- GMainContext *context; /* GLib context used to run the main loop */
- GMainLoop *main_loop; /* GLib main loop */
- gboolean initialized; /* To avoid informing the UI multiple times about the initialization */
- UIView *ui_video_view; /* UIView that holds the video */
- GstState state; /* Current pipeline state */
- gint64 duration; /* Cached clip duration */
+ id ui_delegate; /* Class that we use to interact with the user interface */
+ GstElement *pipeline; /* The running pipeline */
+ GstElement *video_sink; /* The video sink element which receives XOverlay commands */
+ GMainContext *context; /* GLib context used to run the main loop */
+ GMainLoop *main_loop; /* GLib main loop */
+ gboolean initialized; /* To avoid informing the UI multiple times about the initialization */
+ UIView *ui_video_view; /* UIView that holds the video */
+ GstState state; /* Current pipeline state */
+ gint64 duration; /* Cached clip duration */
+ gint64 desired_position; /* Position to seek to, once the pipeline is running */
+ GstClockTime last_seek_time; /* For seeking overflow prevention (throttling) */
}
/*
@@ -73,6 +79,17 @@ GST_DEBUG_CATEGORY_STATIC (debug_category);
GST_DEBUG ("URI set to %s", char_uri);
}
+-(void) setPosition:(NSInteger)milliseconds
+{
+ gint64 position = (gint64)(milliseconds * GST_MSECOND);
+ if (state >= GST_STATE_PAUSED) {
+ execute_seek(position, self);
+ } else {
+ GST_DEBUG ("Scheduling seek to %" GST_TIME_FORMAT " for later", GST_TIME_ARGS (position));
+ self->desired_position = position;
+ }
+}
+
/*
* Private methods
*/
@@ -108,9 +125,7 @@ static gboolean refresh_ui (GStreamerBackend *self) {
/* If we didn't know it yet, query the stream duration */
if (!GST_CLOCK_TIME_IS_VALID (self->duration)) {
- if (!gst_element_query_duration (self->pipeline, &fmt, &self->duration)) {
- GST_WARNING ("Could not query current duration");
- }
+ gst_element_query_duration (self->pipeline, &fmt, &self->duration);
}
if (gst_element_query_position (self->pipeline, &fmt, &position)) {
@@ -120,6 +135,51 @@ static gboolean refresh_ui (GStreamerBackend *self) {
return TRUE;
}
+/* Forward declaration for the delayed seek callback */
+static gboolean delayed_seek_cb (GStreamerBackend *self);
+
+/* Perform seek, if we are not too close to the previous seek. Otherwise, schedule the seek for
+ * some time in the future. */
+static void execute_seek (gint64 position, GStreamerBackend *self) {
+ gint64 diff;
+
+ if (position == GST_CLOCK_TIME_NONE)
+ return;
+
+ diff = gst_util_get_timestamp () - self->last_seek_time;
+
+ if (GST_CLOCK_TIME_IS_VALID (self->last_seek_time) && diff < SEEK_MIN_DELAY) {
+ /* The previous seek was too close, delay this one */
+ GSource *timeout_source;
+
+ if (self->desired_position == GST_CLOCK_TIME_NONE) {
+ /* There was no previous seek scheduled. Setup a timer for some time in the future */
+ timeout_source = g_timeout_source_new ((SEEK_MIN_DELAY - diff) / GST_MSECOND);
+ g_source_set_callback (timeout_source, (GSourceFunc)delayed_seek_cb, (__bridge void *)self, NULL);
+ g_source_attach (timeout_source, self->context);
+ g_source_unref (timeout_source);
+ }
+ /* Update the desired seek position. If multiple petitions are received before it is time
+ * to perform a seek, only the last one is remembered. */
+ self->desired_position = position;
+ GST_DEBUG ("Throttling seek to %" GST_TIME_FORMAT ", will be in %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (position), GST_TIME_ARGS (SEEK_MIN_DELAY - diff));
+ } else {
+ /* Perform the seek now */
+ GST_DEBUG ("Seeking to %" GST_TIME_FORMAT, GST_TIME_ARGS (position));
+ self->last_seek_time = gst_util_get_timestamp ();
+ gst_element_seek_simple (self->pipeline, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, position);
+ self->desired_position = GST_CLOCK_TIME_NONE;
+ }
+}
+
+/* Delayed seek callback. This gets called by the timer setup in the above function. */
+static gboolean delayed_seek_cb (GStreamerBackend *self) {
+ GST_DEBUG ("Doing delayed seek to %" GST_TIME_FORMAT, GST_TIME_ARGS (self->desired_position));
+ execute_seek (self->desired_position, self);
+ return FALSE;
+}
+
static void check_media_size (GStreamerBackend *self) {
GstElement *video_sink;
GstPad *video_sink_pad;
@@ -130,6 +190,10 @@ static void check_media_size (GStreamerBackend *self) {
/* Retrieve the Caps at the entrance of the video sink */
g_object_get (self->pipeline, "video-sink", &video_sink, NULL);
+
+ /* Do nothing if there is no video sink (this might be an audio-only clip */
+ if (!video_sink) return;
+
video_sink_pad = gst_element_get_static_pad (video_sink, "sink");
caps = gst_pad_get_negotiated_caps (video_sink_pad);
@@ -182,6 +246,10 @@ static void state_changed_cb (GstBus *bus, GstMessage *msg, GStreamerBackend *se
if (old_state == GST_STATE_READY && new_state == GST_STATE_PAUSED)
{
check_media_size(self);
+
+ /* If there was a scheduled seek, perform it now that we have moved to the Paused state */
+ if (GST_CLOCK_TIME_IS_VALID (self->desired_position))
+ execute_seek (self->desired_position, self);
}
}
}
diff --git a/gst-sdk/tutorials/xcode iOS/Tutorial 4/LibraryViewController.m b/gst-sdk/tutorials/xcode iOS/Tutorial 4/LibraryViewController.m
index faa57ec..837f010 100644
--- a/gst-sdk/tutorials/xcode iOS/Tutorial 4/LibraryViewController.m
+++ b/gst-sdk/tutorials/xcode iOS/Tutorial 4/LibraryViewController.m
@@ -97,10 +97,31 @@ static NSString *CellIdentifier = @"CellIdentifier";
[entries addObject:[NSString stringWithFormat:@"%@/%@",docsPath, e]];
}
self->mediaEntries = entries;
- self->onlineEntries = [NSArray arrayWithObjects:
- @"http://docs.gstreamer.com/media/sintel_trailer-368p.ogv",
- @"http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_640x360.m4v",
- nil];
+
+ entries = [[NSMutableArray alloc] init];
+
+ // Big Buck Bunny
+ [entries addObject:@"http://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_surround-fix.avi"];
+ [entries addObject:@"http://mirrorblender.top-ix.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_h264.mov"];
+ [entries addObject:@"http://mirrorblender.top-ix.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_stereo.ogg"];
+ [entries addObject:@"http://mirrorblender.top-ix.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_stereo.avi"];
+
+ // Sintel
+ [entries addObject:@"http://ftp.nluug.nl/ftp/graphics/blender/apricot/trailer/Sintel_Trailer1.480p.DivX_Plus_HD.mkv"];
+ [entries addObject:@"http://ftp.nluug.nl/ftp/graphics/blender/apricot/trailer/sintel_trailer-480p.mp4"];
+ [entries addObject:@"http://ftp.nluug.nl/ftp/graphics/blender/apricot/trailer/sintel_trailer-480p.ogv"];
+ [entries addObject:@"http://mirrorblender.top-ix.org/movies/sintel-1024-surround.mp4"];
+
+ // Tears of Steel
+ [entries addObject:@"http://blender-mirror.kino3d.org/mango/download.blender.org/demo/movies/ToS/tears_of_steel_720p.mkv"];
+ [entries addObject:@"http://blender-mirror.kino3d.org/mango/download.blender.org/demo/movies/ToS/tears_of_steel_720p.mov"];
+ [entries addObject:@"http://media.xiph.org/mango/tears_of_steel_1080p.webm"];
+
+ // Radio stations
+ [entries addObject:@"http://radio.hbr1.com:19800/trance.ogg"];
+ [entries addObject:@"http://radio.hbr1.com:19800/tronic.aac"];
+
+ self->onlineEntries = entries;
}
@end
diff --git a/gst-sdk/tutorials/xcode iOS/Tutorial 4/VideoViewController.h b/gst-sdk/tutorials/xcode iOS/Tutorial 4/VideoViewController.h
index fe3d0ff..5935306 100644
--- a/gst-sdk/tutorials/xcode iOS/Tutorial 4/VideoViewController.h
+++ b/gst-sdk/tutorials/xcode iOS/Tutorial 4/VideoViewController.h
@@ -18,6 +18,9 @@
-(IBAction) play:(id)sender;
-(IBAction) pause:(id)sender;
+-(IBAction) sliderValueChanged:(id)sender;
+-(IBAction) sliderTouchDown:(id)sender;
+-(IBAction) sliderTouchUp:(id)sender;
/* From GStreamerBackendDelegate */
-(void) gstreamerInitialized;
diff --git a/gst-sdk/tutorials/xcode iOS/Tutorial 4/VideoViewController.m b/gst-sdk/tutorials/xcode iOS/Tutorial 4/VideoViewController.m
index 57e224c..536082d 100644
--- a/gst-sdk/tutorials/xcode iOS/Tutorial 4/VideoViewController.m
+++ b/gst-sdk/tutorials/xcode iOS/Tutorial 4/VideoViewController.m
@@ -6,6 +6,9 @@
GStreamerBackend *gst_backend;
int media_width;
int media_height;
+ Boolean dragging_slider;
+ Boolean is_local_media;
+ Boolean is_playing_desired;
}
@end
@@ -56,7 +59,7 @@
play_button.enabled = FALSE;
pause_button.enabled = FALSE;
- /* Make these constant for now, later tutorials will change them */
+ /* As soon as the GStreamer backend knows the real values, these ones will be replaced */
media_width = 320;
media_height = 240;
@@ -81,12 +84,37 @@
-(IBAction) play:(id)sender
{
[gst_backend play];
+ is_playing_desired = YES;
}
/* Called when the Pause button is pressed */
-(IBAction) pause:(id)sender
{
[gst_backend pause];
+ is_playing_desired = NO;
+}
+
+- (IBAction)sliderValueChanged:(id)sender {
+ if (!dragging_slider) return;
+ // If this is a local file, allow scrub seeking, this is, seek as soon as the slider is moved.
+ if (is_local_media)
+ [gst_backend setPosition:time_slider.value];
+ [self updateTimeWidget];
+}
+
+- (IBAction)sliderTouchDown:(id)sender {
+ [gst_backend pause];
+ dragging_slider = YES;
+}
+
+- (IBAction)sliderTouchUp:(id)sender {
+ dragging_slider = NO;
+ // If this is a remote file, scrub seeking is probably not going to work smoothly enough.
+ // Therefore, perform only the seek when the slider is released.
+ if (!is_local_media)
+ [gst_backend setPosition:time_slider.value];
+ if (is_playing_desired)
+ [gst_backend play];
}
/* Called when the size of the main view has changed, so we can
@@ -121,6 +149,8 @@
pause_button.enabled = TRUE;
message_label.text = @"Ready";
[gst_backend setUri:uri];
+ is_local_media = [uri hasPrefix:@"file://"];
+ is_playing_desired = NO;
});
}
@@ -144,6 +174,9 @@
-(void) setCurrentPosition:(NSInteger)position duration:(NSInteger)duration
{
+ /* Ignore messages from the pipeline if the time sliders is being dragged */
+ if (dragging_slider) return;
+
dispatch_async(dispatch_get_main_queue(), ^{
time_slider.maximumValue = duration;
time_slider.value = position;
diff --git a/gst-sdk/tutorials/xcode iOS/Tutorial 4/en.lproj/MainStoryboard_iPad.storyboard b/gst-sdk/tutorials/xcode iOS/Tutorial 4/en.lproj/MainStoryboard_iPad.storyboard
index e12bdb7..157ce52 100644
--- a/gst-sdk/tutorials/xcode iOS/Tutorial 4/en.lproj/MainStoryboard_iPad.storyboard
+++ b/gst-sdk/tutorials/xcode iOS/Tutorial 4/en.lproj/MainStoryboard_iPad.storyboard
@@ -80,6 +80,13 @@
<slider key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="1" id="ufs-E5-87w" userLabel="Slider">
<rect key="frame" x="240" y="11" width="118" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <connections>
+ <action selector="sliderTouchDown:" destination="z7O-8l-Zeo" eventType="touchDown" id="yV6-eN-VUb"/>
+ <action selector="sliderTouchUp:" destination="z7O-8l-Zeo" eventType="touchCancel" id="OyS-WZ-sEk"/>
+ <action selector="sliderTouchUp:" destination="z7O-8l-Zeo" eventType="touchUpOutside" id="Vfz-se-pJg"/>
+ <action selector="sliderTouchUp:" destination="z7O-8l-Zeo" eventType="touchUpInside" id="kZF-uW-GRo"/>
+ <action selector="sliderValueChanged:" destination="z7O-8l-Zeo" eventType="valueChanged" id="cwm-pm-BfT"/>
+ </connections>
</slider>
</barButtonItem>
</items>