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 */
}
/*
{
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);
}
}
+/* 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;
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);
-(void) app_function
{
GstBus *bus;
+ GSource *timeout_source;
GSource *bus_source;
GError *error = NULL;
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);
/* 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
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;
@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
*/
[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;
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);
}
/*
});
}
+-(void) setCurrentPosition:(NSInteger)position duration:(NSInteger)duration
+{
+ dispatch_async(dispatch_get_main_queue(), ^{
+ time_slider.maximumValue = duration;
+ time_slider.value = position;
+ [self updateTimeWidget];
+ });
+}
+
@end
<constraint firstAttribute="height" constant="44" type="user" id="EwL-Ma-A4v"/>
</constraints>
<items>
- <barButtonItem style="plain" systemItem="flexibleSpace" id="onU-hf-FS4"/>
<barButtonItem systemItem="play" id="UlF-Ga-2VX">
<connections>
<action selector="play:" destination="z7O-8l-Zeo" id="5xC-uv-9lM"/>
<action selector="pause:" destination="z7O-8l-Zeo" id="BYM-2X-Tel"/>
</connections>
</barButtonItem>
- <barButtonItem style="plain" systemItem="flexibleSpace" id="urI-U7-ALw"/>
+ <barButtonItem style="plain" id="s39-nx-e0L">
+ <textField key="customView" opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="00:00:00 / 00:00:00" borderStyle="roundedRect" textAlignment="center" minimumFontSize="17" id="G8q-Tu-Qx0" userLabel="Time">
+ <rect key="frame" x="90" y="7" width="140" height="30"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <textInputTraits key="textInputTraits"/>
+ </textField>
+ </barButtonItem>
+ <barButtonItem style="plain" id="2n0-TO-8Ss">
+ <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"/>
+ </slider>
+ </barButtonItem>
</items>
</toolbar>
</subviews>
<outlet property="message_label" destination="iLX-h1-Ko5" id="Q0Y-3J-zis"/>
<outlet property="pause_button" destination="J3O-8j-Tkt" id="Dls-sg-FPm"/>
<outlet property="play_button" destination="UlF-Ga-2VX" id="243-yq-GEe"/>
+ <outlet property="time_label" destination="G8q-Tu-Qx0" id="jpR-6u-zp0"/>
+ <outlet property="time_slider" destination="ufs-E5-87w" id="ZUd-jO-4qN"/>
+ <outlet property="toolbar" destination="MUi-CE-Ydy" id="Lme-0P-4Xq"/>
<outlet property="video_container_view" destination="xWd-bg-0b6" id="7dL-Mp-QGc"/>
<outlet property="video_height_constraint" destination="A9A-eK-7QX" id="rMe-ze-8l5"/>
<outlet property="video_view" destination="6tN-97-YoQ" id="Q0n-dR-hqv"/>
<point key="canvasLocation" x="-478" y="-199"/>
</scene>
</scenes>
- <classes>
- <class className="EaglUIView" superclassName="UIView">
- <source key="sourceIdentifier" type="project" relativePath="./Classes/EaglUIView.h"/>
- </class>
- <class className="LibraryViewController" superclassName="UITableViewController">
- <source key="sourceIdentifier" type="project" relativePath="./Classes/LibraryViewController.h"/>
- <relationships>
- <relationship kind="action" name="refresh:"/>
- </relationships>
- </class>
- <class className="VideoViewController" superclassName="UIViewController">
- <source key="sourceIdentifier" type="project" relativePath="./Classes/VideoViewController.h"/>
- <relationships>
- <relationship kind="action" name="pause:"/>
- <relationship kind="action" name="play:"/>
- <relationship kind="outlet" name="message_label" candidateClass="UILabel"/>
- <relationship kind="outlet" name="pause_button" candidateClass="UIBarButtonItem"/>
- <relationship kind="outlet" name="play_button" candidateClass="UIBarButtonItem"/>
- <relationship kind="outlet" name="video_container_view" candidateClass="UIView"/>
- <relationship kind="outlet" name="video_height_constraint" candidateClass="NSLayoutConstraint"/>
- <relationship kind="outlet" name="video_view" candidateClass="UIView"/>
- <relationship kind="outlet" name="video_width_constraint" candidateClass="NSLayoutConstraint"/>
- </relationships>
- </class>
- </classes>
<simulatedMetricsContainer key="defaultSimulatedMetrics">
<simulatedStatusBarMetrics key="statusBar" statusBarStyle="blackTranslucent"/>
<simulatedOrientationMetrics key="orientation"/>
<action selector="pause:" destination="iMo-Z9-PrL" id="mef-Ij-Agl"/>
</connections>
</barButtonItem>
+ <barButtonItem style="plain" id="VUJ-y8-aWS">
+ <textField key="customView" opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" enabled="NO" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="00:00:00 / 00:00:00" borderStyle="roundedRect" textAlignment="center" minimumFontSize="17" id="R6T-PH-VPd" userLabel="Time">
+ <rect key="frame" x="132" y="7" width="139" height="30"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <textInputTraits key="textInputTraits"/>
+ </textField>
+ </barButtonItem>
<barButtonItem style="plain" systemItem="flexibleSpace" id="LSl-TA-0qV"/>
</items>
</toolbar>
+ <slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="4Ns-t9-gs7" userLabel="Slider"/>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
- <constraint firstItem="A2w-65-QSF" firstAttribute="bottom" secondItem="zgN-eK-M4Q" secondAttribute="bottom" constant="20" symbolic="YES" type="user" id="0OY-rR-W60"/>
- <constraint firstItem="LmB-wv-Ztp" firstAttribute="top" secondItem="nA3-W2-kn7" secondAttribute="bottom" type="user" id="7xY-sq-nDU"/>
+ <constraint firstItem="LmB-wv-Ztp" firstAttribute="top" secondItem="nA3-W2-kn7" secondAttribute="bottom" type="user" id="5TL-Gq-YnE"/>
<constraint firstItem="A2w-65-QSF" firstAttribute="leading" secondItem="zgN-eK-M4Q" secondAttribute="leading" constant="20" symbolic="YES" type="user" id="Aki-Hx-2C9"/>
+ <constraint firstItem="4Ns-t9-gs7" firstAttribute="trailing" secondItem="zgN-eK-M4Q" secondAttribute="trailing" type="default" id="HFi-uV-wdo"/>
+ <constraint firstItem="4Ns-t9-gs7" firstAttribute="bottom" secondItem="A2w-65-QSF" secondAttribute="top" type="default" id="Iml-yC-EDE"/>
+ <constraint firstItem="4Ns-t9-gs7" firstAttribute="top" secondItem="LmB-wv-Ztp" secondAttribute="bottom" type="user" id="TUy-Wk-veP"/>
<constraint firstItem="A2w-65-QSF" firstAttribute="trailing" secondItem="zgN-eK-M4Q" secondAttribute="trailing" constant="20" symbolic="YES" type="user" id="WWj-l6-D2k"/>
- <constraint firstItem="LmB-wv-Ztp" firstAttribute="bottom" secondItem="A2w-65-QSF" secondAttribute="top" constant="8" symbolic="YES" type="user" id="cGe-Ok-mYU"/>
<constraint firstAttribute="trailing" secondItem="LmB-wv-Ztp" secondAttribute="trailing" constant="20" symbolic="YES" type="user" id="ddw-6a-Ccz"/>
+ <constraint firstItem="A2w-65-QSF" firstAttribute="bottom" secondItem="zgN-eK-M4Q" secondAttribute="bottom" type="default" id="e3s-lP-iPh"/>
<constraint firstAttribute="trailing" secondItem="nA3-W2-kn7" secondAttribute="trailing" type="user" id="lOJ-ew-ZyI"/>
<constraint firstItem="nA3-W2-kn7" firstAttribute="top" secondItem="zgN-eK-M4Q" secondAttribute="top" type="user" id="lUb-ik-h6u"/>
+ <constraint firstItem="4Ns-t9-gs7" firstAttribute="leading" secondItem="zgN-eK-M4Q" secondAttribute="leading" type="default" id="ma4-AV-cQs"/>
<constraint firstItem="LmB-wv-Ztp" firstAttribute="leading" secondItem="zgN-eK-M4Q" secondAttribute="leading" constant="20" symbolic="YES" type="user" id="nfT-8Y-Tvw"/>
<constraint firstItem="nA3-W2-kn7" firstAttribute="leading" secondItem="zgN-eK-M4Q" secondAttribute="leading" type="user" id="p8G-QE-uZ8"/>
</constraints>
<outlet property="message_label" destination="LmB-wv-Ztp" id="YqJ-GW-DBG"/>
<outlet property="pause_button" destination="nH5-s3-C0i" id="VWV-EW-jB6"/>
<outlet property="play_button" destination="8Yb-MS-rAF" id="5SI-l2-mAJ"/>
+ <outlet property="time_label" destination="R6T-PH-VPd" id="bGs-Zr-rv3"/>
+ <outlet property="time_slider" destination="4Ns-t9-gs7" id="9Ne-1N-clc"/>
<outlet property="video_container_view" destination="nA3-W2-kn7" id="lEY-hP-YHD"/>
<outlet property="video_height_constraint" destination="5z2-ux-czd" id="9R7-fg-G0e"/>
<outlet property="video_view" destination="WHW-sv-bWc" id="c6W-td-a74"/>
<relationship kind="outlet" name="message_label" candidateClass="UILabel"/>
<relationship kind="outlet" name="pause_button" candidateClass="UIBarButtonItem"/>
<relationship kind="outlet" name="play_button" candidateClass="UIBarButtonItem"/>
+ <relationship kind="outlet" name="time_label" candidateClass="UITextField"/>
+ <relationship kind="outlet" name="time_slider" candidateClass="UISlider"/>
+ <relationship kind="outlet" name="toolbar" candidateClass="UIToolbar"/>
<relationship kind="outlet" name="video_container_view" candidateClass="UIView"/>
<relationship kind="outlet" name="video_height_constraint" candidateClass="NSLayoutConstraint"/>
<relationship kind="outlet" name="video_view" candidateClass="UIView"/>