2013-05-17 13:04:35 +00:00
|
|
|
#import "VideoViewController.h"
|
|
|
|
#import "GStreamerBackend.h"
|
|
|
|
#import <UIKit/UIKit.h>
|
|
|
|
|
|
|
|
@interface VideoViewController () {
|
|
|
|
GStreamerBackend *gst_backend;
|
2013-05-21 08:29:18 +00:00
|
|
|
int media_width; /* Width of the clip */
|
|
|
|
int media_height; /* height ofthe clip */
|
|
|
|
Boolean dragging_slider; /* Whether the time slider is being dragged or not */
|
|
|
|
Boolean is_local_media; /* Whether this clip is stored locally or is being streamed */
|
|
|
|
Boolean is_playing_desired; /* Whether the user asked to go to PLAYING */
|
2013-05-17 13:04:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation VideoViewController
|
|
|
|
|
|
|
|
@synthesize uri;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Private methods
|
|
|
|
*/
|
2013-05-21 08:29:18 +00:00
|
|
|
|
|
|
|
/* The text widget acts as an slave for the seek bar, so it reflects what the seek bar shows, whether
|
|
|
|
* it is an actual pipeline position or the position the user is currently dragging to. */
|
2013-05-17 13:04:35 +00:00
|
|
|
- (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
|
|
|
|
*/
|
|
|
|
|
|
|
|
- (void)viewDidLoad
|
|
|
|
{
|
|
|
|
[super viewDidLoad];
|
|
|
|
|
|
|
|
play_button.enabled = FALSE;
|
|
|
|
pause_button.enabled = FALSE;
|
|
|
|
|
|
|
|
/* As soon as the GStreamer backend knows the real values, these ones will be replaced */
|
|
|
|
media_width = 320;
|
|
|
|
media_height = 240;
|
|
|
|
|
|
|
|
gst_backend = [[GStreamerBackend alloc] init:self videoView:video_view];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)viewDidDisappear:(BOOL)animated
|
|
|
|
{
|
|
|
|
if (gst_backend)
|
|
|
|
{
|
|
|
|
[gst_backend deinit];
|
|
|
|
}
|
2013-05-22 11:30:14 +00:00
|
|
|
[UIApplication sharedApplication].idleTimerDisabled = NO;
|
2013-05-17 13:04:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)didReceiveMemoryWarning
|
|
|
|
{
|
|
|
|
[super didReceiveMemoryWarning];
|
|
|
|
// Dispose of any resources that can be recreated.
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Called when the Play button is pressed */
|
|
|
|
-(IBAction) play:(id)sender
|
|
|
|
{
|
|
|
|
[gst_backend play];
|
|
|
|
is_playing_desired = YES;
|
2013-05-22 11:30:14 +00:00
|
|
|
[UIApplication sharedApplication].idleTimerDisabled = YES;
|
2013-05-17 13:04:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Called when the Pause button is pressed */
|
|
|
|
-(IBAction) pause:(id)sender
|
|
|
|
{
|
|
|
|
[gst_backend pause];
|
|
|
|
is_playing_desired = NO;
|
2013-05-22 11:30:14 +00:00
|
|
|
[UIApplication sharedApplication].idleTimerDisabled = NO;
|
2013-05-17 13:04:35 +00:00
|
|
|
}
|
|
|
|
|
2013-05-21 08:29:18 +00:00
|
|
|
/* Called when the time slider position has changed, either because the user dragged it or
|
|
|
|
* we programmatically changed its position. dragging_slider tells us which one happened */
|
2013-05-17 13:04:35 +00:00
|
|
|
- (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];
|
|
|
|
}
|
|
|
|
|
2013-05-21 08:29:18 +00:00
|
|
|
/* Called when the user starts to drag the time slider */
|
2013-05-17 13:04:35 +00:00
|
|
|
- (IBAction)sliderTouchDown:(id)sender {
|
|
|
|
[gst_backend pause];
|
|
|
|
dragging_slider = YES;
|
|
|
|
}
|
|
|
|
|
2013-05-21 08:29:18 +00:00
|
|
|
/* Called when the user stops dragging the time slider */
|
2013-05-17 13:04:35 +00:00
|
|
|
- (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
|
|
|
|
* resize the sub-views in ways not allowed by storyboarding. */
|
|
|
|
- (void)viewDidLayoutSubviews
|
|
|
|
{
|
|
|
|
CGFloat view_width = video_container_view.bounds.size.width;
|
|
|
|
CGFloat view_height = video_container_view.bounds.size.height;
|
|
|
|
|
|
|
|
CGFloat correct_height = view_width * media_height / media_width;
|
|
|
|
CGFloat correct_width = view_height * media_width / media_height;
|
|
|
|
|
|
|
|
if (correct_height < view_height) {
|
|
|
|
video_height_constraint.constant = correct_height;
|
|
|
|
video_width_constraint.constant = view_width;
|
|
|
|
} else {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Methods from GstreamerBackendDelegate
|
|
|
|
*/
|
|
|
|
|
|
|
|
-(void) gstreamerInitialized
|
|
|
|
{
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
play_button.enabled = TRUE;
|
|
|
|
pause_button.enabled = TRUE;
|
|
|
|
message_label.text = @"Ready";
|
|
|
|
[gst_backend setUri:uri];
|
|
|
|
is_local_media = [uri hasPrefix:@"file://"];
|
|
|
|
is_playing_desired = NO;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
-(void) gstreamerSetUIMessage:(NSString *)message
|
|
|
|
{
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
message_label.text = message;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
-(void) mediaSizeChanged:(NSInteger)width height:(NSInteger)height
|
|
|
|
{
|
|
|
|
media_width = width;
|
|
|
|
media_height = height;
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
[self viewDidLayoutSubviews];
|
|
|
|
[video_view setNeedsLayout];
|
|
|
|
[video_view layoutIfNeeded];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
-(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;
|
|
|
|
[self updateTimeWidget];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|