2 * Copyright (C) 2010-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 //hack around problem with xbmc's typedef int BOOL
22 // and obj-c's typedef unsigned char BOOL
23 #define BOOL XBMC_BOOL
24 #include <sys/resource.h>
28 #include "settings/AdvancedSettings.h"
29 #include "settings/Settings.h"
31 #include "MusicInfoTag.h"
32 #include "SpecialProtocol.h"
34 #include "ApplicationMessenger.h"
35 #include "Application.h"
36 #include "interfaces/AnnouncementManager.h"
37 #include "input/touch/generic/GenericTouchActionHandler.h"
38 #include "guilib/GUIControl.h"
39 #include "guilib/Key.h"
40 #include "windowing/WindowingFactory.h"
41 #include "video/VideoReferenceClock.h"
42 #include "utils/log.h"
43 #include "utils/TimeUtils.h"
44 #include "utils/Variant.h"
46 #include "threads/Event.h"
48 #include "TextureCache.h"
53 #define M_PI 3.1415926535897932384626433832795028842
55 #define RADIANS_TO_DEGREES(radians) ((radians) * (180.0 / M_PI))
59 #import <AVFoundation/AVAudioSession.h>
60 #import <MediaPlayer/MPMediaItem.h>
62 #import <MediaPlayer/MPNowPlayingInfoCenter.h>
64 const NSString *MPNowPlayingInfoPropertyElapsedPlaybackTime = @"MPNowPlayingInfoPropertyElapsedPlaybackTime";
65 const NSString *MPNowPlayingInfoPropertyPlaybackRate = @"MPNowPlayingInfoPropertyPlaybackRate";
66 const NSString *MPNowPlayingInfoPropertyPlaybackQueueIndex = @"MPNowPlayingInfoPropertyPlaybackQueueIndex";
67 const NSString *MPNowPlayingInfoPropertyPlaybackQueueCount = @"MPNowPlayingInfoPropertyPlaybackQueueCount";
69 #import "IOSEAGLView.h"
71 #import "XBMCController.h"
72 #import "IOSScreenManager.h"
73 #import "XBMCApplication.h"
74 #import "XBMCDebugHelpers.h"
77 XBMCController *g_xbmcController;
78 static CEvent screenChangeEvent;
81 // notification messages
82 extern NSString* kBRScreenSaverActivated;
83 extern NSString* kBRScreenSaverDismissed;
85 id objectFromVariant(const CVariant &data);
87 NSArray *arrayFromVariantArray(const CVariant &data)
91 NSMutableArray *array = [[[NSMutableArray alloc] initWithCapacity:data.size()] autorelease];
92 for (CVariant::const_iterator_array itr = data.begin_array(); itr != data.end_array(); ++itr)
94 [array addObject:objectFromVariant(*itr)];
99 NSDictionary *dictionaryFromVariantMap(const CVariant &data)
101 if (!data.isObject())
103 NSMutableDictionary *dict = [[[NSMutableDictionary alloc] initWithCapacity:data.size()] autorelease];
104 for (CVariant::const_iterator_map itr = data.begin_map(); itr != data.end_map(); ++itr)
106 [dict setValue:objectFromVariant(itr->second) forKey:[NSString stringWithUTF8String:itr->first.c_str()]];
111 id objectFromVariant(const CVariant &data)
116 return [NSString stringWithUTF8String:data.asString().c_str()];
117 if (data.isWideString())
118 return [NSString stringWithCString:(const char *)data.asWideString().c_str() encoding:NSUnicodeStringEncoding];
119 if (data.isInteger())
120 return [NSNumber numberWithLongLong:data.asInteger()];
121 if (data.isUnsignedInteger())
122 return [NSNumber numberWithUnsignedLongLong:data.asUnsignedInteger()];
123 if (data.isBoolean())
124 return [NSNumber numberWithInt:data.asBoolean()?1:0];
126 return [NSNumber numberWithDouble:data.asDouble()];
128 return arrayFromVariantArray(data);
130 return dictionaryFromVariantMap(data);
134 void AnnounceBridge(ANNOUNCEMENT::AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data)
136 LOG(@"AnnounceBridge: [%s], [%s], [%s]", ANNOUNCEMENT::AnnouncementFlagToString(flag), sender, message);
137 NSDictionary *dict = dictionaryFromVariantMap(data);
138 LOG(@"data: %@", dict.description);
139 const std::string msg(message);
142 NSDictionary *item = [dict valueForKey:@"item"];
143 NSDictionary *player = [dict valueForKey:@"player"];
144 [item setValue:[player valueForKey:@"speed"] forKey:@"speed"];
145 std::string thumb = g_application.CurrentFileItem().GetArt("thumb");
149 CStdString cachedThumb(CTextureCache::Get().CheckCachedImage(thumb, false, needsRecaching));
150 LOG("thumb: %s, %s", thumb.c_str(), cachedThumb.c_str());
151 if (!cachedThumb.empty())
153 CStdString thumbRealPath = CSpecialProtocol::TranslatePath(cachedThumb);
154 [item setValue:[NSString stringWithUTF8String:thumbRealPath.c_str()] forKey:@"thumb"];
157 double duration = g_application.GetTotalTime();
159 [item setValue:[NSNumber numberWithDouble:duration] forKey:@"duration"];
160 [item setValue:[NSNumber numberWithDouble:g_application.GetTime()] forKey:@"elapsed"];
161 int current = g_playlistPlayer.GetCurrentSong();
164 [item setValue:[NSNumber numberWithInt:current] forKey:@"current"];
165 [item setValue:[NSNumber numberWithInt:g_playlistPlayer.GetPlaylist(g_playlistPlayer.GetCurrentPlaylist()).size()] forKey:@"total"];
167 if (g_application.CurrentFileItem().HasMusicInfoTag())
169 const std::vector<std::string> &genre = g_application.CurrentFileItem().GetMusicInfoTag()->GetGenre();
172 NSMutableArray *genreArray = [[NSMutableArray alloc] initWithCapacity:genre.size()];
173 for(std::vector<std::string>::const_iterator it = genre.begin(); it != genre.end(); ++it)
175 [genreArray addObject:[NSString stringWithUTF8String:it->c_str()]];
177 [item setValue:genreArray forKey:@"genre"];
180 LOG(@"item: %@", item.description);
181 [g_xbmcController performSelectorOnMainThread:@selector(onPlay:) withObject:item waitUntilDone:NO];
183 else if (msg == "OnSpeedChanged")
185 NSDictionary *item = [dict valueForKey:@"item"];
186 NSDictionary *player = [dict valueForKey:@"player"];
187 [item setValue:[player valueForKey:@"speed"] forKey:@"speed"];
188 [item setValue:[NSNumber numberWithDouble:g_application.GetTime()] forKey:@"elapsed"];
189 LOG(@"item: %@", item.description);
190 [g_xbmcController performSelectorOnMainThread:@selector(OnSpeedChanged:) withObject:item waitUntilDone:NO];
192 else if (msg == "OnPause")
194 [g_xbmcController performSelectorOnMainThread:@selector(onPause:) withObject:[dict valueForKey:@"item"] waitUntilDone:NO];
196 else if (msg == "OnStop")
198 [g_xbmcController performSelectorOnMainThread:@selector(onStop:) withObject:[dict valueForKey:@"item"] waitUntilDone:NO];
202 class AnnounceReceiver : public ANNOUNCEMENT::IAnnouncer
205 virtual void Announce(ANNOUNCEMENT::AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data)
207 // not all Announce called from xbmc main thread, we need an auto poll here.
209 AnnounceBridge(flag, sender, message, data);
211 virtual ~AnnounceReceiver() {}
214 if (NULL==g_announceReceiver) {
215 g_announceReceiver = new AnnounceReceiver();
216 ANNOUNCEMENT::CAnnouncementManager::AddAnnouncer(g_announceReceiver);
219 static void dealloc()
221 ANNOUNCEMENT::CAnnouncementManager::RemoveAnnouncer(g_announceReceiver);
222 delete g_announceReceiver;
225 AnnounceReceiver() {}
226 static AnnounceReceiver *g_announceReceiver;
229 AnnounceReceiver *AnnounceReceiver::g_announceReceiver = NULL;
231 //--------------------------------------------------------------
234 @interface XBMCController ()
235 - (void)rescheduleNetworkAutoSuspend;
238 @interface UIApplication (extended)
239 -(void) terminateWithSuccess;
242 @implementation XBMCController
243 @synthesize animating;
244 @synthesize lastGesturePoint;
245 @synthesize screenScale;
246 @synthesize touchBeginSignaled;
247 @synthesize m_screenIdx;
248 @synthesize screensize;
249 @synthesize m_networkAutoSuspendTimer;
250 @synthesize nowPlayingInfo;
251 //--------------------------------------------------------------
252 - (void) sendKeypressEvent: (XBMC_Event) event
254 event.type = XBMC_KEYDOWN;
255 CWinEvents::MessagePush(&event);
257 event.type = XBMC_KEYUP;
258 CWinEvents::MessagePush(&event);
261 // START OF UIKeyInput protocol
267 - (void)insertText:(NSString *)text
270 memset(&newEvent, 0, sizeof(newEvent));
271 unichar currentKey = [text characterAtIndex:0];
273 // handle upper case letters
274 if (currentKey >= 'A' && currentKey <= 'Z')
276 newEvent.key.keysym.mod = XBMCKMOD_LSHIFT;
277 currentKey += 0x20;// convert to lower case
281 if (currentKey == '\n' || currentKey == '\r')
282 currentKey = XBMCK_RETURN;
284 newEvent.key.keysym.sym = (XBMCKey)currentKey;
285 newEvent.key.keysym.unicode = currentKey;
287 [self sendKeypressEvent:newEvent];
290 - (void)deleteBackward
292 [self sendKey:XBMCK_BACKSPACE];
294 // END OF UIKeyInput protocol
296 // - iOS6 rotation API - will be called on iOS7 runtime!--------
297 - (NSUInteger)supportedInterfaceOrientations
299 //mask defines available as of ios6 sdk
300 //return UIInterfaceOrientationMaskLandscape;
301 return (1 << UIInterfaceOrientationLandscapeLeft) | (1 << UIInterfaceOrientationLandscapeRight);
303 // - old rotation API will be called on iOS6 and lower - removed in iOS7
304 -(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
306 //on external screens somehow the logic is rotated by 90°
307 //so we have to do this with our supported orientations then aswell
308 if([[IOSScreenManager sharedInstance] isExternalScreen])
310 if(interfaceOrientation == UIInterfaceOrientationPortrait)
315 else//internal screen
317 if(interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
321 else if(interfaceOrientation == UIInterfaceOrientationLandscapeRight)
328 //--------------------------------------------------------------
329 - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
331 orientation = toInterfaceOrientation;
332 CGRect srect = [IOSScreenManager getLandscapeResolution: [m_glView getCurrentScreen]];
333 CGRect rect = srect;;
336 switch(toInterfaceOrientation)
338 case UIInterfaceOrientationPortrait:
339 case UIInterfaceOrientationPortraitUpsideDown:
340 if(![[IOSScreenManager sharedInstance] isExternalScreen])
342 rect.size = CGSizeMake( srect.size.height, srect.size.width );
345 case UIInterfaceOrientationLandscapeLeft:
346 case UIInterfaceOrientationLandscapeRight:
347 break;//just leave the rect as is
349 m_glView.frame = rect;
352 - (UIInterfaceOrientation) getOrientation
357 -(void)sendKey:(XBMCKey) key
360 memset(&newEvent, 0, sizeof(newEvent));
362 //newEvent.key.keysym.unicode = key;
363 newEvent.key.keysym.sym = key;
364 [self sendKeypressEvent:newEvent];
367 //--------------------------------------------------------------
368 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
370 if ([gestureRecognizer isKindOfClass:[UIRotationGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]]) {
374 if ([gestureRecognizer isKindOfClass:[UISwipeGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
381 //--------------------------------------------------------------
382 - (void)createGestureRecognizers
384 //1 finger single tab
385 UITapGestureRecognizer *singleFingerSingleTap = [[UITapGestureRecognizer alloc]
386 initWithTarget:self action:@selector(handleSingleFingerSingleTap:)];
388 singleFingerSingleTap.delaysTouchesBegan = NO;
389 singleFingerSingleTap.numberOfTapsRequired = 1;
390 singleFingerSingleTap.numberOfTouchesRequired = 1;
392 [m_glView addGestureRecognizer:singleFingerSingleTap];
393 [singleFingerSingleTap release];
395 //2 finger single tab - right mouse
396 //single finger double tab delays single finger single tab - so we
397 //go for 2 fingers here - so single finger single tap is instant
398 UITapGestureRecognizer *doubleFingerSingleTap = [[UITapGestureRecognizer alloc]
399 initWithTarget:self action:@selector(handleDoubleFingerSingleTap:)];
401 doubleFingerSingleTap.delaysTouchesBegan = NO;
402 doubleFingerSingleTap.numberOfTapsRequired = 1;
403 doubleFingerSingleTap.numberOfTouchesRequired = 2;
404 [m_glView addGestureRecognizer:doubleFingerSingleTap];
405 [doubleFingerSingleTap release];
407 //1 finger single long tab - right mouse - alernative
408 UILongPressGestureRecognizer *singleFingerSingleLongTap = [[UILongPressGestureRecognizer alloc]
409 initWithTarget:self action:@selector(handleSingleFingerSingleLongTap:)];
411 singleFingerSingleLongTap.delaysTouchesBegan = NO;
412 singleFingerSingleLongTap.delaysTouchesEnded = NO;
413 [m_glView addGestureRecognizer:singleFingerSingleLongTap];
414 [singleFingerSingleLongTap release];
416 //double finger swipe left for backspace ... i like this fast backspace feature ;)
417 UISwipeGestureRecognizer *swipeLeft2 = [[UISwipeGestureRecognizer alloc]
418 initWithTarget:self action:@selector(handleSwipe:)];
420 swipeLeft2.delaysTouchesBegan = NO;
421 swipeLeft2.numberOfTouchesRequired = 2;
422 swipeLeft2.direction = UISwipeGestureRecognizerDirectionLeft;
423 swipeLeft2.delegate = self;
424 [m_glView addGestureRecognizer:swipeLeft2];
425 [swipeLeft2 release];
427 //single finger swipe left
428 UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc]
429 initWithTarget:self action:@selector(handleSwipe:)];
431 swipeLeft.delaysTouchesBegan = NO;
432 swipeLeft.numberOfTouchesRequired = 1;
433 swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
434 swipeLeft.delegate = self;
435 [m_glView addGestureRecognizer:swipeLeft];
438 //single finger swipe right
439 UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc]
440 initWithTarget:self action:@selector(handleSwipe:)];
442 swipeRight.delaysTouchesBegan = NO;
443 swipeRight.numberOfTouchesRequired = 1;
444 swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
445 swipeRight.delegate = self;
446 [m_glView addGestureRecognizer:swipeRight];
447 [swipeRight release];
449 //single finger swipe up
450 UISwipeGestureRecognizer *swipeUp = [[UISwipeGestureRecognizer alloc]
451 initWithTarget:self action:@selector(handleSwipe:)];
453 swipeUp.delaysTouchesBegan = NO;
454 swipeUp.numberOfTouchesRequired = 1;
455 swipeUp.direction = UISwipeGestureRecognizerDirectionUp;
456 swipeUp.delegate = self;
457 [m_glView addGestureRecognizer:swipeUp];
460 //single finger swipe down
461 UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc]
462 initWithTarget:self action:@selector(handleSwipe:)];
464 swipeDown.delaysTouchesBegan = NO;
465 swipeDown.numberOfTouchesRequired = 1;
466 swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
467 swipeDown.delegate = self;
468 [m_glView addGestureRecognizer:swipeDown];
471 //for pan gestures with one finger
472 UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]
473 initWithTarget:self action:@selector(handlePan:)];
475 pan.delaysTouchesBegan = NO;
476 pan.maximumNumberOfTouches = 1;
477 [m_glView addGestureRecognizer:pan];
481 UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc]
482 initWithTarget:self action:@selector(handlePinch:)];
484 pinch.delaysTouchesBegan = NO;
485 pinch.delegate = self;
486 [m_glView addGestureRecognizer:pinch];
490 UIRotationGestureRecognizer *rotate = [[UIRotationGestureRecognizer alloc]
491 initWithTarget:self action:@selector(handleRotate:)];
493 rotate.delaysTouchesBegan = NO;
494 rotate.delegate = self;
495 [m_glView addGestureRecognizer:rotate];
498 //--------------------------------------------------------------
499 - (void) activateKeyboard:(UIView *)view
501 [self.view addSubview:view];
502 m_glView.userInteractionEnabled = NO;
504 //--------------------------------------------------------------
505 - (void) deactivateKeyboard:(UIView *)view
507 [view removeFromSuperview];
508 m_glView.userInteractionEnabled = YES;
509 [self becomeFirstResponder];
511 //--------------------------------------------------------------
512 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
514 if( [m_glView isXBMCAlive] )//NO GESTURES BEFORE WE ARE UP AND RUNNING
516 UITouch *touch = (UITouch *)[[touches allObjects] objectAtIndex:0];
517 CGPoint point = [touch locationInView:m_glView];
518 point.x *= screenScale;
519 point.y *= screenScale;
520 CGenericTouchActionHandler::Get().OnSingleTouchStart(point.x, point.y);
523 //--------------------------------------------------------------
524 -(void)handlePinch:(UIPinchGestureRecognizer*)sender
526 if( [m_glView isXBMCAlive] )//NO GESTURES BEFORE WE ARE UP AND RUNNING
528 CGPoint point = [sender locationOfTouch:0 inView:m_glView];
529 point.x *= screenScale;
530 point.y *= screenScale;
534 case UIGestureRecognizerStateBegan:
535 CGenericTouchActionHandler::Get().OnTouchGestureStart(point.x, point.y);
537 case UIGestureRecognizerStateChanged:
538 CGenericTouchActionHandler::Get().OnZoomPinch(point.x, point.y, [sender scale]);
540 case UIGestureRecognizerStateEnded:
541 case UIGestureRecognizerStateCancelled:
542 CGenericTouchActionHandler::Get().OnTouchGestureEnd(point.x, point.y, 0, 0, 0, 0);
549 //--------------------------------------------------------------
550 -(void)handleRotate:(UIRotationGestureRecognizer*)sender
552 if( [m_glView isXBMCAlive] )//NO GESTURES BEFORE WE ARE UP AND RUNNING
554 CGPoint point = [sender locationOfTouch:0 inView:m_glView];
555 point.x *= screenScale;
556 point.y *= screenScale;
560 case UIGestureRecognizerStateBegan:
561 CGenericTouchActionHandler::Get().OnTouchGestureStart(point.x, point.y);
563 case UIGestureRecognizerStateChanged:
564 CGenericTouchActionHandler::Get().OnRotate(point.x, point.y, RADIANS_TO_DEGREES([sender rotation]));
566 case UIGestureRecognizerStateEnded:
567 CGenericTouchActionHandler::Get().OnTouchGestureEnd(point.x, point.y, 0, 0, 0, 0);
574 //--------------------------------------------------------------
575 - (IBAction)handlePan:(UIPanGestureRecognizer *)sender
577 if( [m_glView isXBMCAlive] )//NO GESTURES BEFORE WE ARE UP AND RUNNING
579 CGPoint velocity = [sender velocityInView:m_glView];
581 if( [sender state] == UIGestureRecognizerStateBegan )
583 CGPoint point = [sender locationOfTouch:0 inView:m_glView];
584 point.x *= screenScale;
585 point.y *= screenScale;
586 touchBeginSignaled = false;
587 lastGesturePoint = point;
590 if( [sender state] == UIGestureRecognizerStateChanged )
592 CGPoint point = [sender locationOfTouch:0 inView:m_glView];
593 point.x *= screenScale;
594 point.y *= screenScale;
595 bool bNotify = false;
596 CGFloat yMovement=point.y - lastGesturePoint.y;
597 CGFloat xMovement=point.x - lastGesturePoint.x;
611 if( !touchBeginSignaled )
613 CGenericTouchActionHandler::Get().OnTouchGestureStart((float)point.x, (float)point.y);
614 touchBeginSignaled = true;
617 CGenericTouchActionHandler::Get().OnTouchGesturePan((float)point.x, (float)point.y,
618 (float)xMovement, (float)yMovement,
619 (float)velocity.x, (float)velocity.y);
620 lastGesturePoint = point;
624 if( touchBeginSignaled && ([sender state] == UIGestureRecognizerStateEnded || [sender state] == UIGestureRecognizerStateCancelled))
626 //signal end of pan - this will start inertial scrolling with deacceleration in CApplication
627 CGenericTouchActionHandler::Get().OnTouchGestureEnd((float)lastGesturePoint.x, (float)lastGesturePoint.y,
628 (float)0.0, (float)0.0,
629 (float)velocity.x, (float)velocity.y);
631 touchBeginSignaled = false;
635 //--------------------------------------------------------------
636 - (IBAction)handleSwipe:(UISwipeGestureRecognizer *)sender
638 if( [m_glView isXBMCAlive] )//NO GESTURES BEFORE WE ARE UP AND RUNNING
642 if (sender.state == UIGestureRecognizerStateRecognized)
644 CGPoint point = [sender locationOfTouch:0 inView:m_glView];
645 point.x *= screenScale;
646 point.y *= screenScale;
648 TouchMoveDirection direction = TouchMoveDirectionNone;
649 switch ([sender direction])
651 case UISwipeGestureRecognizerDirectionRight:
652 direction = TouchMoveDirectionRight;
654 case UISwipeGestureRecognizerDirectionLeft:
655 direction = TouchMoveDirectionLeft;
657 case UISwipeGestureRecognizerDirectionUp:
658 direction = TouchMoveDirectionUp;
660 case UISwipeGestureRecognizerDirectionDown:
661 direction = TouchMoveDirectionDown;
664 CGenericTouchActionHandler::Get().OnSwipe(direction,
666 point.x, point.y, 0, 0,
667 [sender numberOfTouches]);
671 //--------------------------------------------------------------
672 - (IBAction)handleSingleFingerSingleTap:(UIGestureRecognizer *)sender
674 if( [m_glView isXBMCAlive] )//NO GESTURES BEFORE WE ARE UP AND RUNNING
676 CGPoint point = [sender locationOfTouch:0 inView:m_glView];
677 point.x *= screenScale;
678 point.y *= screenScale;
679 //NSLog(@"%s singleTap", __PRETTY_FUNCTION__);
680 CGenericTouchActionHandler::Get().OnTap((float)point.x, (float)point.y, [sender numberOfTouches]);
683 //--------------------------------------------------------------
684 - (IBAction)handleDoubleFingerSingleTap:(UIGestureRecognizer *)sender
686 if( [m_glView isXBMCAlive] )//NO GESTURES BEFORE WE ARE UP AND RUNNING
688 CGPoint point = [sender locationOfTouch:0 inView:m_glView];
689 point.x *= screenScale;
690 point.y *= screenScale;
691 //NSLog(@"%s toubleTap", __PRETTY_FUNCTION__);
692 CGenericTouchActionHandler::Get().OnTap((float)point.x, (float)point.y, [sender numberOfTouches]);
695 //--------------------------------------------------------------
696 - (IBAction)handleSingleFingerSingleLongTap:(UIGestureRecognizer *)sender
698 if( [m_glView isXBMCAlive] )//NO GESTURES BEFORE WE ARE UP AND RUNNING
700 CGPoint point = [sender locationOfTouch:0 inView:m_glView];
701 point.x *= screenScale;
702 point.y *= screenScale;
704 if (sender.state == UIGestureRecognizerStateBegan)
706 lastGesturePoint = point;
708 //CGenericTouchActionHandler::Get().OnSingleTouchStart((float)point.x, (float)point.y);
711 if (sender.state == UIGestureRecognizerStateEnded)
713 CGenericTouchActionHandler::Get().OnSingleTouchMove((float)point.x, (float)point.y, point.x - lastGesturePoint.x, point.y - lastGesturePoint.y, 0, 0);
716 if (sender.state == UIGestureRecognizerStateEnded)
718 CGenericTouchActionHandler::Get().OnLongPress((float)point.x, (float)point.y);
722 //--------------------------------------------------------------
723 - (id)initWithFrame:(CGRect)frame withScreen:(UIScreen *)screen
731 m_isPlayingBeforeInactive = NO;
732 m_bgTask = UIBackgroundTaskInvalid;
733 m_playbackState = IOS_PLAYBACK_STOPPED;
735 m_window = [[UIWindow alloc] initWithFrame:frame];
736 [m_window setRootViewController:self];
737 m_window.screen = screen;
738 /* Turn off autoresizing */
739 m_window.autoresizingMask = 0;
740 m_window.autoresizesSubviews = NO;
742 NSNotificationCenter *center;
743 center = [NSNotificationCenter defaultCenter];
744 [center addObserver: self
745 selector: @selector(observeDefaultCenterStuff:)
749 /* We start in landscape mode */
750 CGRect srect = frame;
751 srect.size = CGSizeMake( frame.size.height, frame.size.width );
752 orientation = UIInterfaceOrientationLandscapeLeft;
754 m_glView = [[IOSEAGLView alloc] initWithFrame: srect withScreen:screen];
755 [[IOSScreenManager sharedInstance] setView:m_glView];
756 [m_glView setMultipleTouchEnabled:YES];
758 /* Check if screen is Retina */
759 screenScale = [m_glView getScreenScale:screen];
761 [self.view addSubview: m_glView];
763 [self createGestureRecognizers];
764 [m_window addSubview: self.view];
765 [m_window makeKeyAndVisible];
766 g_xbmcController = self;
768 AnnounceReceiver::init();
772 //--------------------------------------------------------------
777 //--------------------------------------------------------------
780 // stop background task
781 [m_networkAutoSuspendTimer invalidate];
782 [self enableNetworkAutoSuspend:nil];
784 AnnounceReceiver::dealloc();
785 [m_glView stopAnimation];
789 NSNotificationCenter *center;
790 // take us off the default center for our app
791 center = [NSNotificationCenter defaultCenter];
792 [center removeObserver: self];
796 //--------------------------------------------------------------
797 - (void)viewWillAppear:(BOOL)animated
801 // move this later into CocoaPowerSyscall
802 [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
804 [self resumeAnimation];
806 [super viewWillAppear:animated];
808 //--------------------------------------------------------------
809 -(void) viewDidAppear:(BOOL)animated
811 [super viewDidAppear:animated];
813 [self becomeFirstResponder];
814 [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
816 //--------------------------------------------------------------
817 - (void)viewWillDisappear:(BOOL)animated
821 [self pauseAnimation];
823 // move this later into CocoaPowerSyscall
824 [[UIApplication sharedApplication] setIdleTimerDisabled:NO];
826 [super viewWillDisappear:animated];
828 //--------------------------------------------------------------
831 // override our input view to an empty view
832 // this prevents the on screen keyboard
833 // which would be shown whenever this UIResponder
834 // becomes the first responder (which is always the case!)
835 // caused by implementing the UIKeyInput protocol
836 return [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
838 //--------------------------------------------------------------
839 - (BOOL) canBecomeFirstResponder
843 //--------------------------------------------------------------
844 - (void)viewDidUnload
846 [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
847 [self resignFirstResponder];
849 [super viewDidUnload];
851 //--------------------------------------------------------------
852 - (void) initDisplayLink
854 [m_glView initDisplayLink];
856 //--------------------------------------------------------------
857 - (void) deinitDisplayLink
859 [m_glView deinitDisplayLink];
861 //--------------------------------------------------------------
862 - (double) getDisplayLinkFPS;
864 return [m_glView getDisplayLinkFPS];
866 //--------------------------------------------------------------
867 - (void) setFramebuffer
869 [m_glView setFramebuffer];
871 //--------------------------------------------------------------
872 - (bool) presentFramebuffer
874 return [m_glView presentFramebuffer];
876 //--------------------------------------------------------------
877 - (CGSize) getScreenSize
879 screensize.width = m_glView.bounds.size.width * screenScale;
880 screensize.height = m_glView.bounds.size.height * screenScale;
883 //--------------------------------------------------------------
884 - (CGFloat) getScreenScale:(UIScreen *)screen;
886 return [m_glView getScreenScale:screen];
888 //--------------------------------------------------------------
889 //--------------------------------------------------------------
890 - (BOOL) recreateOnReselect
895 //--------------------------------------------------------------
896 - (void)didReceiveMemoryWarning
898 // Releases the view if it doesn't have a superview.
899 [super didReceiveMemoryWarning];
901 // Release any cached data, images, etc. that aren't in use.
903 //--------------------------------------------------------------
904 - (void)disableNetworkAutoSuspend
907 if (m_bgTask != UIBackgroundTaskInvalid)
909 [[UIApplication sharedApplication] endBackgroundTask: m_bgTask];
910 m_bgTask = UIBackgroundTaskInvalid;
912 // we have to alloc the background task for keep network working after screen lock and dark.
913 UIBackgroundTaskIdentifier newTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
916 if (m_networkAutoSuspendTimer)
918 [m_networkAutoSuspendTimer invalidate];
919 self.m_networkAutoSuspendTimer = nil;
922 //--------------------------------------------------------------
923 - (void)enableNetworkAutoSuspend:(id)obj
926 if (m_bgTask != UIBackgroundTaskInvalid)
928 [[UIApplication sharedApplication] endBackgroundTask: m_bgTask];
929 m_bgTask = UIBackgroundTaskInvalid;
932 //--------------------------------------------------------------
933 - (void) disableSystemSleep
936 //--------------------------------------------------------------
937 - (void) enableSystemSleep
940 //--------------------------------------------------------------
941 - (void) disableScreenSaver
944 //--------------------------------------------------------------
945 - (void) enableScreenSaver
948 //--------------------------------------------------------------
949 - (bool) changeScreen: (unsigned int)screenIdx withMode:(UIScreenMode *)mode
953 ret = [[IOSScreenManager sharedInstance] changeScreen:screenIdx withMode:mode];
957 //--------------------------------------------------------------
958 - (void) activateScreen: (UIScreen *)screen withOrientation:(UIInterfaceOrientation)newOrientation
960 // Since ios7 we have to handle the orientation manually
961 // it differs by 90 degree between internal and external screen
963 UIView *view = [m_window.subviews objectAtIndex:0];
964 switch(newOrientation)
966 case UIInterfaceOrientationPortrait:
969 case UIInterfaceOrientationPortraitUpsideDown:
972 case UIInterfaceOrientationLandscapeLeft:
975 case UIInterfaceOrientationLandscapeRight:
979 // reset the rotation of the view
980 view.layer.transform = CATransform3DMakeRotation(angle, 0, 0.0, 1.0);
981 [view setFrame:m_window.frame];
982 m_window.screen = screen;
984 //--------------------------------------------------------------
985 - (void) remoteControlReceivedWithEvent: (UIEvent *) receivedEvent {
986 LOG(@"%s: type %d, subtype: %d", __PRETTY_FUNCTION__, receivedEvent.type, receivedEvent.subtype);
987 if (receivedEvent.type == UIEventTypeRemoteControl)
989 [self disableNetworkAutoSuspend];
990 switch (receivedEvent.subtype)
992 case UIEventSubtypeRemoteControlTogglePlayPause:
993 CApplicationMessenger::Get().SendAction(ACTION_PLAYER_PLAYPAUSE);
995 case UIEventSubtypeRemoteControlPlay:
996 CApplicationMessenger::Get().SendAction(ACTION_PLAYER_PLAY);
998 case UIEventSubtypeRemoteControlPause:
999 // ACTION_PAUSE sometimes cause unpause, use MediaPauseIfPlaying to make sure pause only
1000 CApplicationMessenger::Get().MediaPauseIfPlaying();
1002 case UIEventSubtypeRemoteControlNextTrack:
1003 CApplicationMessenger::Get().SendAction(ACTION_NEXT_ITEM);
1005 case UIEventSubtypeRemoteControlPreviousTrack:
1006 CApplicationMessenger::Get().SendAction(ACTION_PREV_ITEM);
1008 case UIEventSubtypeRemoteControlBeginSeekingForward:
1009 // use 4X speed forward.
1010 CApplicationMessenger::Get().SendAction(ACTION_PLAYER_FORWARD);
1011 CApplicationMessenger::Get().SendAction(ACTION_PLAYER_FORWARD);
1013 case UIEventSubtypeRemoteControlBeginSeekingBackward:
1014 // use 4X speed rewind.
1015 CApplicationMessenger::Get().SendAction(ACTION_PLAYER_REWIND);
1016 CApplicationMessenger::Get().SendAction(ACTION_PLAYER_REWIND);
1018 case UIEventSubtypeRemoteControlEndSeekingForward:
1019 case UIEventSubtypeRemoteControlEndSeekingBackward:
1020 // restore to normal playback speed.
1021 if (g_application.m_pPlayer->IsPlaying() && !g_application.m_pPlayer->IsPaused())
1022 CApplicationMessenger::Get().SendAction(ACTION_PLAYER_PLAY);
1025 LOG(@"unhandled subtype: %d", receivedEvent.subtype);
1028 [self rescheduleNetworkAutoSuspend];
1031 //--------------------------------------------------------------
1032 - (void)enterBackground
1035 if (g_application.m_pPlayer->IsPlaying() && !g_application.m_pPlayer->IsPaused())
1037 m_isPlayingBeforeInactive = YES;
1038 CApplicationMessenger::Get().MediaPauseIfPlaying();
1042 - (void)enterForeground
1045 // when we come back, restore playing if we were.
1046 if (m_isPlayingBeforeInactive)
1048 CApplicationMessenger::Get().MediaUnPause();
1049 m_isPlayingBeforeInactive = NO;
1053 - (void)becomeInactive
1055 // if we were interrupted, already paused here
1056 // else if user background us or lock screen, only pause video here, audio keep playing.
1057 if (g_application.m_pPlayer->IsPlayingVideo() && !g_application.m_pPlayer->IsPaused())
1059 m_isPlayingBeforeInactive = YES;
1060 CApplicationMessenger::Get().MediaPauseIfPlaying();
1062 // check whether we need disable network auto suspend.
1063 [self rescheduleNetworkAutoSuspend];
1065 //--------------------------------------------------------------
1066 - (void)pauseAnimation
1070 [m_glView pauseAnimation];
1072 //--------------------------------------------------------------
1073 - (void)resumeAnimation
1077 [m_glView resumeAnimation];
1079 //--------------------------------------------------------------
1080 - (void)startAnimation
1084 [m_glView startAnimation];
1086 //--------------------------------------------------------------
1087 - (void)stopAnimation
1091 [m_glView stopAnimation];
1093 //--------------------------------------------------------------
1094 - (void)setIOSNowPlayingInfo:(NSDictionary *)info
1096 self.nowPlayingInfo = info;
1097 // MPNowPlayingInfoCenter is an ios5+ class, following code will work on ios5 even if compiled by xcode3
1098 Class NowPlayingInfoCenter = NSClassFromString(@"MPNowPlayingInfoCenter");
1099 if (NowPlayingInfoCenter)
1100 [[NowPlayingInfoCenter defaultCenter] setNowPlayingInfo:self.nowPlayingInfo];
1102 //--------------------------------------------------------------
1103 - (void)onPlay:(NSDictionary *)item
1106 NSMutableDictionary * dict = [[NSMutableDictionary alloc] init];
1108 NSString *title = [item objectForKey:@"title"];
1109 if (title && title.length > 0)
1110 [dict setObject:title forKey:MPMediaItemPropertyTitle];
1111 NSString *album = [item objectForKey:@"album"];
1112 if (album && album.length > 0)
1113 [dict setObject:album forKey:MPMediaItemPropertyAlbumTitle];
1114 NSArray *artists = [item objectForKey:@"artist"];
1115 if (artists && artists.count > 0)
1116 [dict setObject:[artists componentsJoinedByString:@" "] forKey:MPMediaItemPropertyArtist];
1117 NSNumber *track = [item objectForKey:@"track"];
1119 [dict setObject:track forKey:MPMediaItemPropertyAlbumTrackNumber];
1120 NSNumber *duration = [item objectForKey:@"duration"];
1122 [dict setObject:duration forKey:MPMediaItemPropertyPlaybackDuration];
1123 NSArray *genres = [item objectForKey:@"genre"];
1124 if (genres && genres.count > 0)
1125 [dict setObject:[genres componentsJoinedByString:@" "] forKey:MPMediaItemPropertyGenre];
1127 if (NSClassFromString(@"MPNowPlayingInfoCenter"))
1129 NSString *thumb = [item objectForKey:@"thumb"];
1130 if (thumb && thumb.length > 0)
1132 UIImage *image = [UIImage imageWithContentsOfFile:thumb];
1135 MPMediaItemArtwork *mArt = [[MPMediaItemArtwork alloc] initWithImage:image];
1138 [dict setObject:mArt forKey:MPMediaItemPropertyArtwork];
1143 // these proprity keys are ios5+ only
1144 NSNumber *elapsed = [item objectForKey:@"elapsed"];
1146 [dict setObject:elapsed forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
1147 NSNumber *speed = [item objectForKey:@"speed"];
1149 [dict setObject:speed forKey:MPNowPlayingInfoPropertyPlaybackRate];
1150 NSNumber *current = [item objectForKey:@"current"];
1152 [dict setObject:current forKey:MPNowPlayingInfoPropertyPlaybackQueueIndex];
1153 NSNumber *total = [item objectForKey:@"total"];
1155 [dict setObject:total forKey:MPNowPlayingInfoPropertyPlaybackQueueCount];
1158 other properities can be set:
1159 MPMediaItemPropertyAlbumTrackCount
1160 MPMediaItemPropertyComposer
1161 MPMediaItemPropertyDiscCount
1162 MPMediaItemPropertyDiscNumber
1163 MPMediaItemPropertyPersistentID
1165 Additional metadata properties:
1166 MPNowPlayingInfoPropertyChapterNumber;
1167 MPNowPlayingInfoPropertyChapterCount;
1170 [self setIOSNowPlayingInfo:dict];
1173 m_playbackState = IOS_PLAYBACK_PLAYING;
1174 [self disableNetworkAutoSuspend];
1176 //--------------------------------------------------------------
1177 - (void)OnSpeedChanged:(NSDictionary *)item
1180 if (NSClassFromString(@"MPNowPlayingInfoCenter"))
1182 NSMutableDictionary *info = [self.nowPlayingInfo mutableCopy];
1183 NSNumber *elapsed = [item objectForKey:@"elapsed"];
1185 [info setObject:elapsed forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
1186 NSNumber *speed = [item objectForKey:@"speed"];
1188 [info setObject:speed forKey:MPNowPlayingInfoPropertyPlaybackRate];
1190 [self setIOSNowPlayingInfo:info];
1193 //--------------------------------------------------------------
1194 - (void)onPause:(NSDictionary *)item
1197 m_playbackState = IOS_PLAYBACK_PAUSED;
1198 // schedule set network auto suspend state for save power if idle.
1199 [self rescheduleNetworkAutoSuspend];
1201 //--------------------------------------------------------------
1202 - (void)onStop:(NSDictionary *)item
1205 [self setIOSNowPlayingInfo:nil];
1207 m_playbackState = IOS_PLAYBACK_STOPPED;
1208 // delay set network auto suspend state in case we are switching playing item.
1209 [self rescheduleNetworkAutoSuspend];
1211 //--------------------------------------------------------------
1212 - (void)rescheduleNetworkAutoSuspend
1214 LOG(@"%s: playback state: %d", __PRETTY_FUNCTION__, m_playbackState);
1215 if (m_playbackState == IOS_PLAYBACK_PLAYING)
1217 [self disableNetworkAutoSuspend];
1220 if (m_networkAutoSuspendTimer)
1221 [m_networkAutoSuspendTimer invalidate];
1223 int delay = m_playbackState == IOS_PLAYBACK_PAUSED ? 60 : 30; // wait longer if paused than stopped
1224 self.m_networkAutoSuspendTimer = [NSTimer scheduledTimerWithTimeInterval:delay target:self selector:@selector(enableNetworkAutoSuspend:) userInfo:nil repeats:NO];
1228 #pragma mark private helper methods
1230 - (void)observeDefaultCenterStuff: (NSNotification *) notification
1232 // LOG(@"default: %@", [notification name]);
1233 // LOG(@"userInfo: %@", [notification userInfo]);