From 76f7e541ec56739d918b056e9f6b7503ab86ba5d Mon Sep 17 00:00:00 2001 From: Xavi Artigas Date: Fri, 17 May 2013 11:46:48 +0200 Subject: [PATCH] Enable seeking by dragging the time slider. Add a bunch of online clips to the hardcoded library. --- .../xcode iOS/Tutorial 4/GStreamerBackend.h | 3 + .../xcode iOS/Tutorial 4/GStreamerBackend.m | 92 +++++++++++++++++++--- .../xcode iOS/Tutorial 4/LibraryViewController.m | 29 ++++++- .../xcode iOS/Tutorial 4/VideoViewController.h | 3 + .../xcode iOS/Tutorial 4/VideoViewController.m | 35 +++++++- .../en.lproj/MainStoryboard_iPad.storyboard | 7 ++ 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 @@ + + + + + + + -- 2.7.4