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" || msg == "OnPause")
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];
191 if (msg == "OnPause")
192 [g_xbmcController performSelectorOnMainThread:@selector(onPause:) withObject:[dict valueForKey:@"item"] waitUntilDone:NO];
194 else if (msg == "OnStop")
196 [g_xbmcController performSelectorOnMainThread:@selector(onStop:) withObject:[dict valueForKey:@"item"] waitUntilDone:NO];
200 class AnnounceReceiver : public ANNOUNCEMENT::IAnnouncer
203 virtual void Announce(ANNOUNCEMENT::AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data)
205 // not all Announce called from xbmc main thread, we need an auto poll here.
207 AnnounceBridge(flag, sender, message, data);
209 virtual ~AnnounceReceiver() {}
212 if (NULL==g_announceReceiver) {
213 g_announceReceiver = new AnnounceReceiver();
214 ANNOUNCEMENT::CAnnouncementManager::AddAnnouncer(g_announceReceiver);
217 static void dealloc()
219 ANNOUNCEMENT::CAnnouncementManager::RemoveAnnouncer(g_announceReceiver);
220 delete g_announceReceiver;
223 AnnounceReceiver() {}
224 static AnnounceReceiver *g_announceReceiver;
227 AnnounceReceiver *AnnounceReceiver::g_announceReceiver = NULL;
229 //--------------------------------------------------------------
232 @interface XBMCController ()
233 - (void)rescheduleNetworkAutoSuspend;
236 @interface UIApplication (extended)
237 -(void) terminateWithSuccess;
240 @implementation XBMCController
241 @synthesize animating;
242 @synthesize lastGesturePoint;
243 @synthesize screenScale;
244 @synthesize touchBeginSignaled;
245 @synthesize m_screenIdx;
246 @synthesize screensize;
247 @synthesize m_networkAutoSuspendTimer;
248 @synthesize nowPlayingInfo;
249 //--------------------------------------------------------------
250 - (void) sendKeypressEvent: (XBMC_Event) event
252 event.type = XBMC_KEYDOWN;
253 CWinEvents::MessagePush(&event);
255 event.type = XBMC_KEYUP;
256 CWinEvents::MessagePush(&event);
259 // START OF UIKeyInput protocol
265 - (void)insertText:(NSString *)text
268 memset(&newEvent, 0, sizeof(newEvent));
269 unichar currentKey = [text characterAtIndex:0];
271 // handle upper case letters
272 if (currentKey >= 'A' && currentKey <= 'Z')
274 newEvent.key.keysym.mod = XBMCKMOD_LSHIFT;
275 currentKey += 0x20;// convert to lower case
279 if (currentKey == '\n' || currentKey == '\r')
280 currentKey = XBMCK_RETURN;
282 newEvent.key.keysym.sym = (XBMCKey)currentKey;
283 newEvent.key.keysym.unicode = currentKey;
285 [self sendKeypressEvent:newEvent];
288 - (void)deleteBackward
290 [self sendKey:XBMCK_BACKSPACE];
292 // END OF UIKeyInput protocol
294 // - iOS6 rotation API - will be called on iOS7 runtime!--------
295 - (NSUInteger)supportedInterfaceOrientations
297 //mask defines available as of ios6 sdk
298 //return UIInterfaceOrientationMaskLandscape;
299 return (1 << UIInterfaceOrientationLandscapeLeft) | (1 << UIInterfaceOrientationLandscapeRight);
301 // - old rotation API will be called on iOS6 and lower - removed in iOS7
302 -(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
304 //on external screens somehow the logic is rotated by 90°
305 //so we have to do this with our supported orientations then aswell
306 if([[IOSScreenManager sharedInstance] isExternalScreen])
308 if(interfaceOrientation == UIInterfaceOrientationPortrait)
313 else//internal screen
315 if(interfaceOrientation == UIInterfaceOrientationLandscapeLeft)
319 else if(interfaceOrientation == UIInterfaceOrientationLandscapeRight)
326 //--------------------------------------------------------------
327 - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
329 orientation = toInterfaceOrientation;
330 CGRect srect = [IOSScreenManager getLandscapeResolution: [m_glView getCurrentScreen]];
331 CGRect rect = srect;;
334 switch(toInterfaceOrientation)
336 case UIInterfaceOrientationPortrait:
337 case UIInterfaceOrientationPortraitUpsideDown:
338 if(![[IOSScreenManager sharedInstance] isExternalScreen])
340 rect.size = CGSizeMake( srect.size.height, srect.size.width );
343 case UIInterfaceOrientationLandscapeLeft:
344 case UIInterfaceOrientationLandscapeRight:
345 break;//just leave the rect as is
347 m_glView.frame = rect;
350 - (UIInterfaceOrientation) getOrientation
355 -(void)sendKey:(XBMCKey) key
358 memset(&newEvent, 0, sizeof(newEvent));
360 //newEvent.key.keysym.unicode = key;
361 newEvent.key.keysym.sym = key;
362 [self sendKeypressEvent:newEvent];
365 //--------------------------------------------------------------
366 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
368 if ([gestureRecognizer isKindOfClass:[UIRotationGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]]) {
372 if ([gestureRecognizer isKindOfClass:[UISwipeGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
379 //--------------------------------------------------------------
380 - (void)createGestureRecognizers
382 //1 finger single tab
383 UITapGestureRecognizer *singleFingerSingleTap = [[UITapGestureRecognizer alloc]
384 initWithTarget:self action:@selector(handleSingleFingerSingleTap:)];
386 singleFingerSingleTap.delaysTouchesBegan = NO;
387 singleFingerSingleTap.numberOfTapsRequired = 1;
388 singleFingerSingleTap.numberOfTouchesRequired = 1;
390 [m_glView addGestureRecognizer:singleFingerSingleTap];
391 [singleFingerSingleTap release];
393 //2 finger single tab - right mouse
394 //single finger double tab delays single finger single tab - so we
395 //go for 2 fingers here - so single finger single tap is instant
396 UITapGestureRecognizer *doubleFingerSingleTap = [[UITapGestureRecognizer alloc]
397 initWithTarget:self action:@selector(handleDoubleFingerSingleTap:)];
399 doubleFingerSingleTap.delaysTouchesBegan = NO;
400 doubleFingerSingleTap.numberOfTapsRequired = 1;
401 doubleFingerSingleTap.numberOfTouchesRequired = 2;
402 [m_glView addGestureRecognizer:doubleFingerSingleTap];
403 [doubleFingerSingleTap release];
405 //1 finger single long tab - right mouse - alernative
406 UILongPressGestureRecognizer *singleFingerSingleLongTap = [[UILongPressGestureRecognizer alloc]
407 initWithTarget:self action:@selector(handleSingleFingerSingleLongTap:)];
409 singleFingerSingleLongTap.delaysTouchesBegan = NO;
410 singleFingerSingleLongTap.delaysTouchesEnded = NO;
411 [m_glView addGestureRecognizer:singleFingerSingleLongTap];
412 [singleFingerSingleLongTap release];
414 //double finger swipe left for backspace ... i like this fast backspace feature ;)
415 UISwipeGestureRecognizer *swipeLeft2 = [[UISwipeGestureRecognizer alloc]
416 initWithTarget:self action:@selector(handleSwipe:)];
418 swipeLeft2.delaysTouchesBegan = NO;
419 swipeLeft2.numberOfTouchesRequired = 2;
420 swipeLeft2.direction = UISwipeGestureRecognizerDirectionLeft;
421 swipeLeft2.delegate = self;
422 [m_glView addGestureRecognizer:swipeLeft2];
423 [swipeLeft2 release];
425 //single finger swipe left
426 UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc]
427 initWithTarget:self action:@selector(handleSwipe:)];
429 swipeLeft.delaysTouchesBegan = NO;
430 swipeLeft.numberOfTouchesRequired = 1;
431 swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
432 swipeLeft.delegate = self;
433 [m_glView addGestureRecognizer:swipeLeft];
436 //single finger swipe right
437 UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc]
438 initWithTarget:self action:@selector(handleSwipe:)];
440 swipeRight.delaysTouchesBegan = NO;
441 swipeRight.numberOfTouchesRequired = 1;
442 swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
443 swipeRight.delegate = self;
444 [m_glView addGestureRecognizer:swipeRight];
445 [swipeRight release];
447 //single finger swipe up
448 UISwipeGestureRecognizer *swipeUp = [[UISwipeGestureRecognizer alloc]
449 initWithTarget:self action:@selector(handleSwipe:)];
451 swipeUp.delaysTouchesBegan = NO;
452 swipeUp.numberOfTouchesRequired = 1;
453 swipeUp.direction = UISwipeGestureRecognizerDirectionUp;
454 swipeUp.delegate = self;
455 [m_glView addGestureRecognizer:swipeUp];
458 //single finger swipe down
459 UISwipeGestureRecognizer *swipeDown = [[UISwipeGestureRecognizer alloc]
460 initWithTarget:self action:@selector(handleSwipe:)];
462 swipeDown.delaysTouchesBegan = NO;
463 swipeDown.numberOfTouchesRequired = 1;
464 swipeDown.direction = UISwipeGestureRecognizerDirectionDown;
465 swipeDown.delegate = self;
466 [m_glView addGestureRecognizer:swipeDown];
469 //for pan gestures with one finger
470 UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]
471 initWithTarget:self action:@selector(handlePan:)];
473 pan.delaysTouchesBegan = NO;
474 pan.maximumNumberOfTouches = 1;
475 [m_glView addGestureRecognizer:pan];
479 UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc]
480 initWithTarget:self action:@selector(handlePinch:)];
482 pinch.delaysTouchesBegan = NO;
483 pinch.delegate = self;
484 [m_glView addGestureRecognizer:pinch];
488 UIRotationGestureRecognizer *rotate = [[UIRotationGestureRecognizer alloc]
489 initWithTarget:self action:@selector(handleRotate:)];
491 rotate.delaysTouchesBegan = NO;
492 rotate.delegate = self;
493 [m_glView addGestureRecognizer:rotate];
496 //--------------------------------------------------------------
497 - (void) activateKeyboard:(UIView *)view
499 [self.view addSubview:view];
500 m_glView.userInteractionEnabled = NO;
502 //--------------------------------------------------------------
503 - (void) deactivateKeyboard:(UIView *)view
505 [view removeFromSuperview];
506 m_glView.userInteractionEnabled = YES;
507 [self becomeFirstResponder];
509 //--------------------------------------------------------------
510 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
512 if( [m_glView isXBMCAlive] )//NO GESTURES BEFORE WE ARE UP AND RUNNING
514 UITouch *touch = (UITouch *)[[touches allObjects] objectAtIndex:0];
515 CGPoint point = [touch locationInView:m_glView];
516 point.x *= screenScale;
517 point.y *= screenScale;
518 CGenericTouchActionHandler::Get().OnSingleTouchStart(point.x, point.y);
521 //--------------------------------------------------------------
522 -(void)handlePinch:(UIPinchGestureRecognizer*)sender
524 if( [m_glView isXBMCAlive] )//NO GESTURES BEFORE WE ARE UP AND RUNNING
526 CGPoint point = [sender locationOfTouch:0 inView:m_glView];
527 point.x *= screenScale;
528 point.y *= screenScale;
532 case UIGestureRecognizerStateBegan:
533 CGenericTouchActionHandler::Get().OnTouchGestureStart(point.x, point.y);
535 case UIGestureRecognizerStateChanged:
536 CGenericTouchActionHandler::Get().OnZoomPinch(point.x, point.y, [sender scale]);
538 case UIGestureRecognizerStateEnded:
539 case UIGestureRecognizerStateCancelled:
540 CGenericTouchActionHandler::Get().OnTouchGestureEnd(point.x, point.y, 0, 0, 0, 0);
547 //--------------------------------------------------------------
548 -(void)handleRotate:(UIRotationGestureRecognizer*)sender
550 if( [m_glView isXBMCAlive] )//NO GESTURES BEFORE WE ARE UP AND RUNNING
552 CGPoint point = [sender locationOfTouch:0 inView:m_glView];
553 point.x *= screenScale;
554 point.y *= screenScale;
558 case UIGestureRecognizerStateBegan:
559 CGenericTouchActionHandler::Get().OnTouchGestureStart(point.x, point.y);
561 case UIGestureRecognizerStateChanged:
562 CGenericTouchActionHandler::Get().OnRotate(point.x, point.y, RADIANS_TO_DEGREES([sender rotation]));
564 case UIGestureRecognizerStateEnded:
565 CGenericTouchActionHandler::Get().OnTouchGestureEnd(point.x, point.y, 0, 0, 0, 0);
572 //--------------------------------------------------------------
573 - (IBAction)handlePan:(UIPanGestureRecognizer *)sender
575 if( [m_glView isXBMCAlive] )//NO GESTURES BEFORE WE ARE UP AND RUNNING
577 CGPoint velocity = [sender velocityInView:m_glView];
579 if( [sender state] == UIGestureRecognizerStateBegan )
581 CGPoint point = [sender locationOfTouch:0 inView:m_glView];
582 point.x *= screenScale;
583 point.y *= screenScale;
584 touchBeginSignaled = false;
585 lastGesturePoint = point;
588 if( [sender state] == UIGestureRecognizerStateChanged )
590 CGPoint point = [sender locationOfTouch:0 inView:m_glView];
591 point.x *= screenScale;
592 point.y *= screenScale;
593 bool bNotify = false;
594 CGFloat yMovement=point.y - lastGesturePoint.y;
595 CGFloat xMovement=point.x - lastGesturePoint.x;
609 if( !touchBeginSignaled )
611 CGenericTouchActionHandler::Get().OnTouchGestureStart((float)point.x, (float)point.y);
612 touchBeginSignaled = true;
615 CGenericTouchActionHandler::Get().OnTouchGesturePan((float)point.x, (float)point.y,
616 (float)xMovement, (float)yMovement,
617 (float)velocity.x, (float)velocity.y);
618 lastGesturePoint = point;
622 if( touchBeginSignaled && ([sender state] == UIGestureRecognizerStateEnded || [sender state] == UIGestureRecognizerStateCancelled))
624 //signal end of pan - this will start inertial scrolling with deacceleration in CApplication
625 CGenericTouchActionHandler::Get().OnTouchGestureEnd((float)lastGesturePoint.x, (float)lastGesturePoint.y,
626 (float)0.0, (float)0.0,
627 (float)velocity.x, (float)velocity.y);
629 touchBeginSignaled = false;
633 //--------------------------------------------------------------
634 - (IBAction)handleSwipe:(UISwipeGestureRecognizer *)sender
636 if( [m_glView isXBMCAlive] )//NO GESTURES BEFORE WE ARE UP AND RUNNING
640 if (sender.state == UIGestureRecognizerStateRecognized)
642 CGPoint point = [sender locationOfTouch:0 inView:m_glView];
643 point.x *= screenScale;
644 point.y *= screenScale;
646 TouchMoveDirection direction = TouchMoveDirectionNone;
647 switch ([sender direction])
649 case UISwipeGestureRecognizerDirectionRight:
650 direction = TouchMoveDirectionRight;
652 case UISwipeGestureRecognizerDirectionLeft:
653 direction = TouchMoveDirectionLeft;
655 case UISwipeGestureRecognizerDirectionUp:
656 direction = TouchMoveDirectionUp;
658 case UISwipeGestureRecognizerDirectionDown:
659 direction = TouchMoveDirectionDown;
662 CGenericTouchActionHandler::Get().OnSwipe(direction,
664 point.x, point.y, 0, 0,
665 [sender numberOfTouches]);
669 //--------------------------------------------------------------
670 - (IBAction)handleSingleFingerSingleTap:(UIGestureRecognizer *)sender
672 if( [m_glView isXBMCAlive] )//NO GESTURES BEFORE WE ARE UP AND RUNNING
674 CGPoint point = [sender locationOfTouch:0 inView:m_glView];
675 point.x *= screenScale;
676 point.y *= screenScale;
677 //NSLog(@"%s singleTap", __PRETTY_FUNCTION__);
678 CGenericTouchActionHandler::Get().OnTap((float)point.x, (float)point.y, [sender numberOfTouches]);
681 //--------------------------------------------------------------
682 - (IBAction)handleDoubleFingerSingleTap:(UIGestureRecognizer *)sender
684 if( [m_glView isXBMCAlive] )//NO GESTURES BEFORE WE ARE UP AND RUNNING
686 CGPoint point = [sender locationOfTouch:0 inView:m_glView];
687 point.x *= screenScale;
688 point.y *= screenScale;
689 //NSLog(@"%s toubleTap", __PRETTY_FUNCTION__);
690 CGenericTouchActionHandler::Get().OnTap((float)point.x, (float)point.y, [sender numberOfTouches]);
693 //--------------------------------------------------------------
694 - (IBAction)handleSingleFingerSingleLongTap:(UIGestureRecognizer *)sender
696 if( [m_glView isXBMCAlive] )//NO GESTURES BEFORE WE ARE UP AND RUNNING
698 CGPoint point = [sender locationOfTouch:0 inView:m_glView];
699 point.x *= screenScale;
700 point.y *= screenScale;
702 if (sender.state == UIGestureRecognizerStateBegan)
704 lastGesturePoint = point;
706 //CGenericTouchActionHandler::Get().OnSingleTouchStart((float)point.x, (float)point.y);
709 if (sender.state == UIGestureRecognizerStateEnded)
711 CGenericTouchActionHandler::Get().OnSingleTouchMove((float)point.x, (float)point.y, point.x - lastGesturePoint.x, point.y - lastGesturePoint.y, 0, 0);
714 if (sender.state == UIGestureRecognizerStateEnded)
716 CGenericTouchActionHandler::Get().OnLongPress((float)point.x, (float)point.y);
720 //--------------------------------------------------------------
721 - (id)initWithFrame:(CGRect)frame withScreen:(UIScreen *)screen
729 m_isPlayingBeforeInactive = NO;
730 m_bgTask = UIBackgroundTaskInvalid;
731 m_playbackState = IOS_PLAYBACK_STOPPED;
733 m_window = [[UIWindow alloc] initWithFrame:frame];
734 [m_window setRootViewController:self];
735 m_window.screen = screen;
736 /* Turn off autoresizing */
737 m_window.autoresizingMask = 0;
738 m_window.autoresizesSubviews = NO;
740 NSNotificationCenter *center;
741 center = [NSNotificationCenter defaultCenter];
742 [center addObserver: self
743 selector: @selector(observeDefaultCenterStuff:)
747 /* We start in landscape mode */
748 CGRect srect = frame;
749 srect.size = CGSizeMake( frame.size.height, frame.size.width );
750 orientation = UIInterfaceOrientationLandscapeLeft;
752 m_glView = [[IOSEAGLView alloc] initWithFrame: srect withScreen:screen];
753 [[IOSScreenManager sharedInstance] setView:m_glView];
754 [m_glView setMultipleTouchEnabled:YES];
756 /* Check if screen is Retina */
757 screenScale = [m_glView getScreenScale:screen];
759 [self.view addSubview: m_glView];
761 [self createGestureRecognizers];
762 [m_window addSubview: self.view];
763 [m_window makeKeyAndVisible];
764 g_xbmcController = self;
766 AnnounceReceiver::init();
770 //--------------------------------------------------------------
775 //--------------------------------------------------------------
778 // stop background task
779 [m_networkAutoSuspendTimer invalidate];
780 [self enableNetworkAutoSuspend:nil];
782 AnnounceReceiver::dealloc();
783 [m_glView stopAnimation];
787 NSNotificationCenter *center;
788 // take us off the default center for our app
789 center = [NSNotificationCenter defaultCenter];
790 [center removeObserver: self];
794 //--------------------------------------------------------------
795 - (void)viewWillAppear:(BOOL)animated
799 // move this later into CocoaPowerSyscall
800 [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
802 [self resumeAnimation];
804 [super viewWillAppear:animated];
806 //--------------------------------------------------------------
807 -(void) viewDidAppear:(BOOL)animated
809 [super viewDidAppear:animated];
811 [self becomeFirstResponder];
812 [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
814 //--------------------------------------------------------------
815 - (void)viewWillDisappear:(BOOL)animated
819 [self pauseAnimation];
821 // move this later into CocoaPowerSyscall
822 [[UIApplication sharedApplication] setIdleTimerDisabled:NO];
824 [super viewWillDisappear:animated];
826 //--------------------------------------------------------------
829 // override our input view to an empty view
830 // this prevents the on screen keyboard
831 // which would be shown whenever this UIResponder
832 // becomes the first responder (which is always the case!)
833 // caused by implementing the UIKeyInput protocol
834 return [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
836 //--------------------------------------------------------------
837 - (BOOL) canBecomeFirstResponder
841 //--------------------------------------------------------------
842 - (void)viewDidUnload
844 [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
845 [self resignFirstResponder];
847 [super viewDidUnload];
849 //--------------------------------------------------------------
850 - (void) initDisplayLink
852 [m_glView initDisplayLink];
854 //--------------------------------------------------------------
855 - (void) deinitDisplayLink
857 [m_glView deinitDisplayLink];
859 //--------------------------------------------------------------
860 - (double) getDisplayLinkFPS;
862 return [m_glView getDisplayLinkFPS];
864 //--------------------------------------------------------------
865 - (void) setFramebuffer
867 [m_glView setFramebuffer];
869 //--------------------------------------------------------------
870 - (bool) presentFramebuffer
872 return [m_glView presentFramebuffer];
874 //--------------------------------------------------------------
875 - (CGSize) getScreenSize
877 screensize.width = m_glView.bounds.size.width * screenScale;
878 screensize.height = m_glView.bounds.size.height * screenScale;
881 //--------------------------------------------------------------
882 - (CGFloat) getScreenScale:(UIScreen *)screen;
884 return [m_glView getScreenScale:screen];
886 //--------------------------------------------------------------
887 //--------------------------------------------------------------
888 - (BOOL) recreateOnReselect
893 //--------------------------------------------------------------
894 - (void)didReceiveMemoryWarning
896 // Releases the view if it doesn't have a superview.
897 [super didReceiveMemoryWarning];
899 // Release any cached data, images, etc. that aren't in use.
901 //--------------------------------------------------------------
902 - (void)disableNetworkAutoSuspend
905 if (m_bgTask != UIBackgroundTaskInvalid)
907 [[UIApplication sharedApplication] endBackgroundTask: m_bgTask];
908 m_bgTask = UIBackgroundTaskInvalid;
910 // we have to alloc the background task for keep network working after screen lock and dark.
911 UIBackgroundTaskIdentifier newTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
914 if (m_networkAutoSuspendTimer)
916 [m_networkAutoSuspendTimer invalidate];
917 self.m_networkAutoSuspendTimer = nil;
920 //--------------------------------------------------------------
921 - (void)enableNetworkAutoSuspend:(id)obj
924 if (m_bgTask != UIBackgroundTaskInvalid)
926 [[UIApplication sharedApplication] endBackgroundTask: m_bgTask];
927 m_bgTask = UIBackgroundTaskInvalid;
930 //--------------------------------------------------------------
931 - (void) disableSystemSleep
934 //--------------------------------------------------------------
935 - (void) enableSystemSleep
938 //--------------------------------------------------------------
939 - (void) disableScreenSaver
942 //--------------------------------------------------------------
943 - (void) enableScreenSaver
946 //--------------------------------------------------------------
947 - (bool) changeScreen: (unsigned int)screenIdx withMode:(UIScreenMode *)mode
951 ret = [[IOSScreenManager sharedInstance] changeScreen:screenIdx withMode:mode];
955 //--------------------------------------------------------------
956 - (void) activateScreen: (UIScreen *)screen withOrientation:(UIInterfaceOrientation)newOrientation
958 // Since ios7 we have to handle the orientation manually
959 // it differs by 90 degree between internal and external screen
961 UIView *view = [m_window.subviews objectAtIndex:0];
962 switch(newOrientation)
964 case UIInterfaceOrientationPortrait:
967 case UIInterfaceOrientationPortraitUpsideDown:
970 case UIInterfaceOrientationLandscapeLeft:
973 case UIInterfaceOrientationLandscapeRight:
977 // reset the rotation of the view
978 view.layer.transform = CATransform3DMakeRotation(angle, 0, 0.0, 1.0);
979 [view setFrame:m_window.frame];
980 m_window.screen = screen;
982 //--------------------------------------------------------------
983 - (void) remoteControlReceivedWithEvent: (UIEvent *) receivedEvent {
984 LOG(@"%s: type %d, subtype: %d", __PRETTY_FUNCTION__, receivedEvent.type, receivedEvent.subtype);
985 if (receivedEvent.type == UIEventTypeRemoteControl)
987 [self disableNetworkAutoSuspend];
988 switch (receivedEvent.subtype)
990 case UIEventSubtypeRemoteControlTogglePlayPause:
991 CApplicationMessenger::Get().SendAction(ACTION_PLAYER_PLAYPAUSE);
993 case UIEventSubtypeRemoteControlPlay:
994 CApplicationMessenger::Get().SendAction(ACTION_PLAYER_PLAY);
996 case UIEventSubtypeRemoteControlPause:
997 // ACTION_PAUSE sometimes cause unpause, use MediaPauseIfPlaying to make sure pause only
998 CApplicationMessenger::Get().MediaPauseIfPlaying();
1000 case UIEventSubtypeRemoteControlNextTrack:
1001 CApplicationMessenger::Get().SendAction(ACTION_NEXT_ITEM);
1003 case UIEventSubtypeRemoteControlPreviousTrack:
1004 CApplicationMessenger::Get().SendAction(ACTION_PREV_ITEM);
1006 case UIEventSubtypeRemoteControlBeginSeekingForward:
1007 // use 4X speed forward.
1008 CApplicationMessenger::Get().SendAction(ACTION_PLAYER_FORWARD);
1009 CApplicationMessenger::Get().SendAction(ACTION_PLAYER_FORWARD);
1011 case UIEventSubtypeRemoteControlBeginSeekingBackward:
1012 // use 4X speed rewind.
1013 CApplicationMessenger::Get().SendAction(ACTION_PLAYER_REWIND);
1014 CApplicationMessenger::Get().SendAction(ACTION_PLAYER_REWIND);
1016 case UIEventSubtypeRemoteControlEndSeekingForward:
1017 case UIEventSubtypeRemoteControlEndSeekingBackward:
1018 // restore to normal playback speed.
1019 if (g_application.m_pPlayer->IsPlaying() && !g_application.m_pPlayer->IsPaused())
1020 CApplicationMessenger::Get().SendAction(ACTION_PLAYER_PLAY);
1023 LOG(@"unhandled subtype: %d", receivedEvent.subtype);
1026 [self rescheduleNetworkAutoSuspend];
1029 //--------------------------------------------------------------
1030 - (void)enterBackground
1033 if (g_application.m_pPlayer->IsPlaying() && !g_application.m_pPlayer->IsPaused())
1035 m_isPlayingBeforeInactive = YES;
1036 CApplicationMessenger::Get().MediaPauseIfPlaying();
1038 g_Windowing.OnAppFocusChange(false);
1041 - (void)enterForeground
1044 g_Windowing.OnAppFocusChange(true);
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]);