diff --git a/gst-sdk/tutorials/xcode iOS/Tutorial 4/GStreamerBackend.m b/gst-sdk/tutorials/xcode iOS/Tutorial 4/GStreamerBackend.m
index 60758cc336..f789edc1ed 100644
--- a/gst-sdk/tutorials/xcode iOS/Tutorial 4/GStreamerBackend.m
+++ b/gst-sdk/tutorials/xcode iOS/Tutorial 4/GStreamerBackend.m
@@ -21,6 +21,8 @@ GST_DEBUG_CATEGORY_STATIC (debug_category);
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 */
}
/*
@@ -33,6 +35,7 @@ GST_DEBUG_CATEGORY_STATIC (debug_category);
{
self->ui_delegate = uiDelegate;
self->ui_video_view = video_view;
+ self->duration = GST_CLOCK_TIME_NONE;
GST_DEBUG_CATEGORY_INIT (debug_category, "tutorial-4", 0, "iOS tutorial 4");
gst_debug_set_threshold_for_name("tutorial-4", GST_LEVEL_DEBUG);
@@ -84,6 +87,39 @@ GST_DEBUG_CATEGORY_STATIC (debug_category);
}
}
+/* Tell the application what is the current position and clip duration */
+-(void) setCurrentUIPosition:(gint)pos duration:(gint)dur
+{
+ if(ui_delegate && [ui_delegate respondsToSelector:@selector(setCurrentPosition:duration:)])
+ {
+ [ui_delegate setCurrentPosition:pos duration:dur];
+ }
+}
+
+/* If we have pipeline and it is running, query the current position and clip duration and inform
+ * the application */
+static gboolean refresh_ui (GStreamerBackend *self) {
+ GstFormat fmt = GST_FORMAT_TIME;
+ gint64 position;
+
+ /* We do not want to update anything unless we have a working pipeline in the PAUSED or PLAYING state */
+ if (!self || !self->pipeline || self->state < GST_STATE_PAUSED)
+ return TRUE;
+
+ /* 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");
+ }
+ }
+
+ if (gst_element_query_position (self->pipeline, &fmt, &position)) {
+ /* The UI expects these values in milliseconds, and GStreamer provides nanoseconds */
+ [self setCurrentUIPosition:position / GST_MSECOND duration:self->duration / GST_MSECOND];
+ }
+ return TRUE;
+}
+
static void check_media_size (GStreamerBackend *self) {
GstElement *video_sink;
GstPad *video_sink_pad;
@@ -138,6 +174,7 @@ static void state_changed_cb (GstBus *bus, GstMessage *msg, GStreamerBackend *se
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
/* Only pay attention to messages coming from the pipeline, not its children */
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (self->pipeline)) {
+ self->state = new_state;
gchar *message = g_strdup_printf("State changed to %s", gst_element_state_get_name(new_state));
[self setUIMessage:message];
g_free (message);
@@ -167,6 +204,7 @@ static void state_changed_cb (GstBus *bus, GstMessage *msg, GStreamerBackend *se
-(void) app_function
{
GstBus *bus;
+ GSource *timeout_source;
GSource *bus_source;
GError *error = NULL;
@@ -205,7 +243,13 @@ static void state_changed_cb (GstBus *bus, GstMessage *msg, GStreamerBackend *se
g_signal_connect (G_OBJECT (bus), "message::error", (GCallback)error_cb, (__bridge void *)self);
g_signal_connect (G_OBJECT (bus), "message::state-changed", (GCallback)state_changed_cb, (__bridge void *)self);
gst_object_unref (bus);
-
+
+ /* Register a function that GLib will call 4 times per second */
+ timeout_source = g_timeout_source_new (250);
+ g_source_set_callback (timeout_source, (GSourceFunc)refresh_ui, (__bridge void *)self, NULL);
+ g_source_attach (timeout_source, context);
+ g_source_unref (timeout_source);
+
/* Create a GLib Main Loop and set it to run */
GST_DEBUG ("Entering main loop...");
main_loop = g_main_loop_new (context, FALSE);
diff --git a/gst-sdk/tutorials/xcode iOS/Tutorial 4/GStreamerBackendDelegate.h b/gst-sdk/tutorials/xcode iOS/Tutorial 4/GStreamerBackendDelegate.h
index 739461cfaa..01981fad95 100644
--- a/gst-sdk/tutorials/xcode iOS/Tutorial 4/GStreamerBackendDelegate.h
+++ b/gst-sdk/tutorials/xcode iOS/Tutorial 4/GStreamerBackendDelegate.h
@@ -14,4 +14,7 @@
/* Called when the media size is first discovered or it changes */
-(void) mediaSizeChanged:(NSInteger)width height:(NSInteger)height;
+/* Called when the media position changes. Times in milliseconds */
+-(void) setCurrentPosition:(NSInteger)position duration:(NSInteger)duration;
+
@end
diff --git a/gst-sdk/tutorials/xcode iOS/Tutorial 4/VideoViewController.h b/gst-sdk/tutorials/xcode iOS/Tutorial 4/VideoViewController.h
index 37064f1541..fe3d0ff6fd 100644
--- a/gst-sdk/tutorials/xcode iOS/Tutorial 4/VideoViewController.h
+++ b/gst-sdk/tutorials/xcode iOS/Tutorial 4/VideoViewController.h
@@ -9,6 +9,9 @@
IBOutlet UIView *video_container_view;
IBOutlet NSLayoutConstraint *video_width_constraint;
IBOutlet NSLayoutConstraint *video_height_constraint;
+ IBOutlet UIToolbar *toolbar;
+ IBOutlet UITextField *time_label;
+ IBOutlet UISlider *time_slider;
}
@property (retain,nonatomic) NSString *uri;
diff --git a/gst-sdk/tutorials/xcode iOS/Tutorial 4/VideoViewController.m b/gst-sdk/tutorials/xcode iOS/Tutorial 4/VideoViewController.m
index 6b4ae04372..57e224c3ab 100644
--- a/gst-sdk/tutorials/xcode iOS/Tutorial 4/VideoViewController.m
+++ b/gst-sdk/tutorials/xcode iOS/Tutorial 4/VideoViewController.m
@@ -14,6 +14,37 @@
@synthesize uri;
+/*
+ * Private methods
+ */
+- (void) updateTimeWidget
+{
+ NSInteger position = time_slider.value / 1000;
+ NSInteger duration = time_slider.maximumValue / 1000;
+ NSString *position_txt = @" -- ";
+ NSString *duration_txt = @" -- ";
+
+ if (duration > 0) {
+ NSUInteger hours = duration / (60 * 60);
+ NSUInteger minutes = (duration / 60) % 60;
+ NSUInteger seconds = duration % 60;
+
+ duration_txt = [NSString stringWithFormat:@"%02u:%02u:%02u", hours, minutes, seconds];
+ }
+ if (position > 0) {
+ NSUInteger hours = position / (60 * 60);
+ NSUInteger minutes = (position / 60) % 60;
+ NSUInteger seconds = position % 60;
+
+ position_txt = [NSString stringWithFormat:@"%02u:%02u:%02u", hours, minutes, seconds];
+ }
+
+ NSString *text = [NSString stringWithFormat:@"%@ / %@",
+ position_txt, duration_txt];
+
+ time_label.text = text;
+}
+
/*
* Methods from UIViewController
*/
@@ -58,6 +89,8 @@
[gst_backend pause];
}
+/* Called when the size of the main view has changed, so we can
+ * resize the sub-views in ways not allowed by storyboarding. */
- (void)viewDidLayoutSubviews
{
CGFloat view_width = video_container_view.bounds.size.width;
@@ -73,6 +106,8 @@
video_width_constraint.constant = correct_width;
video_height_constraint.constant = view_height;
}
+
+ time_slider.frame = CGRectMake(time_slider.frame.origin.x, time_slider.frame.origin.y, toolbar.frame.size.width - time_slider.frame.origin.x - 8, time_slider.frame.size.height);
}
/*
@@ -107,4 +142,13 @@
});
}
+-(void) setCurrentPosition:(NSInteger)position duration:(NSInteger)duration
+{
+ dispatch_async(dispatch_get_main_queue(), ^{
+ time_slider.maximumValue = duration;
+ time_slider.value = position;
+ [self updateTimeWidget];
+ });
+}
+
@end
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 c1b9cf238f..e12bdb7d78 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
@@ -57,7 +57,6 @@
-
@@ -68,7 +67,21 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -92,6 +105,9 @@
+
+
+
@@ -154,31 +170,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/gst-sdk/tutorials/xcode iOS/Tutorial 4/en.lproj/MainStoryboard_iPhone.storyboard b/gst-sdk/tutorials/xcode iOS/Tutorial 4/en.lproj/MainStoryboard_iPhone.storyboard
index 58347d24b7..5d9108be37 100644
--- a/gst-sdk/tutorials/xcode iOS/Tutorial 4/en.lproj/MainStoryboard_iPhone.storyboard
+++ b/gst-sdk/tutorials/xcode iOS/Tutorial 4/en.lproj/MainStoryboard_iPhone.storyboard
@@ -69,20 +69,32 @@
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
-
+
+
@@ -94,6 +106,8 @@
+
+
@@ -178,6 +192,9 @@
+
+
+