Closed GregoryAustinFoster closed 11 years ago
The problem is that on iOS 6 didReceiveResponse callback doesn't get called as often as it does on iOs 5 so the memory buffer doesn't get reallocated. If you look at didReceiveData callback it always assumes that the next image is at the beginning of the buffer, but if the buffer doesn't get reallocated after every image we will just see the first image in the buffer. If the buffer never gets reallocated we will only see the first image and it will look as a freeze. So the solution is to write our own memory management and not to relay on didReceiveResponse. This is my proposal of .m file:
// // MotionJpegImageView.mm // VideoTest // // Created by Matthew Eagar on 10/3/11. // Copyright 2011 ThinkFlood Inc. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is furnished // to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE.
@class CredentialAlertView;
@protocol CredentialAlertDelegate
@end
@interface CredentialAlertView : UIAlertView <UITextFieldDelegate, UIAlertViewDelegate> {
@private
UITextField _usernameField;
UITextField _passwordField;
id
}
@property (nonatomic, readwrite, copy) NSString username;
@property (nonatomic, readwrite, copy) NSString password;
@property (nonatomic, readwrite, assign) id
@end
@implementation CredentialAlertView
@dynamic username; @dynamic password; @synthesize credentialDelegate = _credentialDelegate;
(id)initWithDelegate:(id
if (self) { _credentialDelegate = delegate;
_usernameField = [[UITextField alloc] initWithFrame:CGRectZero];
_usernameField.borderStyle = UITextBorderStyleBezel;
_usernameField.backgroundColor = [UIColor whiteColor];
_usernameField.placeholder = NSLocalizedString(@"UsernamePlaceholderText", @"");
_usernameField.delegate = self;
_usernameField.autocorrectionType = UITextAutocorrectionTypeNo;
_usernameField.autocapitalizationType = UITextAutocapitalizationTypeNone;
_usernameField.returnKeyType = UIReturnKeyNext;
_usernameField.clearButtonMode = UITextFieldViewModeUnlessEditing;
[self addSubview:_usernameField];
[_usernameField release];
_passwordField = [[UITextField alloc] initWithFrame:CGRectZero];
_passwordField.secureTextEntry = YES;
_passwordField.borderStyle = UITextBorderStyleBezel;
_passwordField.backgroundColor = [UIColor whiteColor];
_passwordField.placeholder = NSLocalizedString(@"PasswordPlaceholderText", @"");
_passwordField.delegate = self;
_passwordField.autocorrectionType = UITextAutocorrectionTypeNo;
_passwordField.autocapitalizationType = UITextAutocapitalizationTypeNone;
_passwordField.returnKeyType = UIReturnKeyDone;
_passwordField.clearButtonMode = UITextFieldViewModeUnlessEditing;
[self addSubview:_passwordField];
[_passwordField release];
}
return self; }
(void)layoutSubviews { [super layoutSubviews];
NSString *username = _usernameField.text; _usernameField.text = @"a"; CGSize textFieldSize = [_usernameField sizeThatFits:CGSizeZero]; _usernameField.text = username;
UILabel titleLabel = nil; UILabel messageLabel = nil; NSMutableArray *buttonViews = [NSMutableArray arrayWithCapacity:3];
for (UIView subview in self.subviews) { if (subview == _usernameField || subview == _passwordField) { // continue } else if ([subview isKindOfClass:[UILabel class]]) { if (titleLabel == nil) { titleLabel = (UILabel )subview; } else if (titleLabel.frame.origin.y > subview.frame.origin.y) { messageLabel = titleLabel; titleLabel = (UILabel )subview; } else { messageLabel = (UILabel )subview; } } else if ([subview isKindOfClass:[UIImageView class]]) { // continue } else if ([subview isKindOfClass:[UITextField class]]) { // continue } else { [buttonViews addObject:subview]; } }
CGFloat buttonViewTop = 0.0; for (UIView *buttonView in buttonViews) { CGRect buttonViewFrame = buttonView.frame; buttonViewFrame.origin.y = self.bounds.size.height - buttonViewFrame.size.height - BUTTON_MARGIN; buttonView.frame = buttonViewFrame; buttonViewTop = CGRectGetMinY(buttonViewFrame); }
CGRect labelFrame = messageLabel.frame; CGRect textFieldFrame = CGRectMake(labelFrame.origin.x, labelFrame.origin.y + labelFrame.size.height + TEXT_FIELD_MARGIN, labelFrame.size.width, textFieldSize.height); _usernameField.frame = textFieldFrame; [self bringSubviewToFront:_usernameField];
textFieldFrame.origin.y += textFieldFrame.size.height + TEXT_FIELD_MARGIN; _passwordField.frame = textFieldFrame; [self bringSubviewToFront:_passwordField]; }
(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex == [self cancelButtonIndex]) { if (_credentialDelegate) { [_credentialDelegate credentialAlertCancelled:self]; } } else if (_credentialDelegate) { [_credentialDelegate credentialAlertSaved:self]; }
[alertView dismissWithClickedButtonIndex:buttonIndex animated:YES]; }
}
}
(BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { return YES; }
(BOOL)textFieldShouldReturn:(UITextField *)textField { if (textField.text.length == 0) { // continue } else if (textField == _usernameField) { [_passwordField becomeFirstResponder]; } else if (textField == _passwordField) { [textField resignFirstResponder]; }
return NO; }
@end
static NSData *_beginMarkerData = nil;
static NSData *_endMarkerData = nil;
@interface MotionJpegImageView ()
@end
@implementation MotionJpegImageView
@synthesize url = _url; @synthesize username = _username; @synthesize password = _password; @synthesize allowSelfSignedCertificates = _allowSelfSignedCertificates; @synthesize allowClearTextCredentials = _allowClearTextCredentials; @dynamic isPlaying;
(id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame];
if (self) { _url = nil; _receivedData = nil; _username = nil; _password = nil; _allowSelfSignedCertificates = NO;
if (_endMarkerData == nil) {
uint8_t endMarker[2] = END_MARKER_BYTES;
_endMarkerData = [[NSData alloc] initWithBytes:endMarker length:2];
}
if(_beginMarkerData == nil){
uint8_t endMarker[2] = BEGIN_MARKER_BYTES;
_beginMarkerData = [[NSData alloc] initWithBytes:endMarker length:2];
}
self.contentMode = UIViewContentModeScaleAspectFit;
}
return self; }
(void)dealloc { if (_connection) { [_connection cancel]; [self cleanupConnection]; }
if (_url) { [_url release]; }
if (_username) { [_username release]; }
if (_password) { [_password release]; }
[super dealloc]; }
(void)play { if (_connection) { // continue } else if (_url) { if (_receivedData) { [_receivedData release]; } _receivedData = [[NSMutableData alloc] init];
_connection = [[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:_url]
delegate:self];
} }
(void)cleanupConnection { if (_connection) { [_connection release]; _connection = nil; }
if (_receivedData) { [_receivedData release]; _receivedData = nil; } }
//- (void)connection:(NSURLConnection )connection didReceiveResponse:(NSURLResponse )response { //// DLog(@""); //}
(void)connection:(NSURLConnection )connection didReceiveData:(NSData )data {
[_receivedData appendData:data];
NSRange beginRange = [_receivedData rangeOfData:_beginMarkerData options:0 range:NSMakeRange(0, _receivedData.length)];
NSRange endRange = [_receivedData rangeOfData:_endMarkerData options:0 range:NSMakeRange(beginRange.location+beginRange.length, _receivedData.length - (beginRange.location+beginRange.length))];
if(endRange.location != NSNotFound){ long endLocation = endRange.location + endRange.length;
if (_receivedData.length >= endLocation){
NSData *imageData = [_receivedData subdataWithRange:NSMakeRange(beginRange.location, endLocation-beginRange.location)];
[_receivedData replaceBytesInRange:NSMakeRange(0, endLocation) withBytes:NULL length:0];
UIImage *receivedImage = [UIImage imageWithData:imageData];
if (receivedImage) {
[self setImage:receivedImage];
}
}
} }
//- (void)connection:(NSURLConnection )connection didReceiveData:(NSData )data {
// [_receivedData appendData:data];
//
// NSRange endRange = [_receivedData rangeOfData:_endMarkerData
// options:0
// range:NSMakeRange(0, _receivedData.length)];
//
// long endLocation = endRange.location + endRange.length;
//
// if (_receivedData.length >= endLocation) {
// NSData imageData = [_receivedData subdataWithRange:NSMakeRange(0, endLocation)];
//
// UIImage receivedImage = [UIImage imageWithData:imageData];
// if (receivedImage) {
// [self setImage:receivedImage];
// }
// }
//}
//- (void)connection:(NSURLConnection )connection didReceiveData:(NSData )data {
// [_receivedData appendData:data];
//
// NSRange endRange = [_receivedData rangeOfData:_endMarkerData
// options:0
// range:NSMakeRange(0, _receivedData.length)];
//
// long endLocation = endRange.location + endRange.length;
//
// if (_receivedData.length >= endLocation) {
// NSData imageData = [_receivedData subdataWithRange:NSMakeRange(0, endLocation)];
//
// UIImage receivedImage = [UIImage imageWithData:imageData];
// if (receivedImage) {
// [self setImage:receivedImage];
// [_receivedData replaceBytesInRange:NSMakeRange(0, endLocation+1) withBytes:NULL length:0];
// // DLog(@"%i", _receivedData.length);
// }
// }
//}
(BOOL)connection:(NSURLConnection )connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace )protectionSpace { BOOL allow = NO; if ([protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { allow = _allowSelfSignedCertificates; } else { allow = _allowClearTextCredentials; }
return allow; }
(void)connection:(NSURLConnection )connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge )challenge { if ([challenge previousFailureCount] == 0 && _username && _username.length > 0 && _password && _password.length > 0) { NSURLCredential *credentials = [NSURLCredential credentialWithUser:_username password:_password persistence:NSURLCredentialPersistenceForSession]; [[challenge sender] useCredential:credentials forAuthenticationChallenge:challenge]; } else { [[challenge sender] cancelAuthenticationChallenge:challenge]; [self cleanupConnection];
CredentialAlertView *loginAlert =
[[CredentialAlertView alloc] initWithDelegate:self
forHost:_url.host];
loginAlert.username = self.username;
[loginAlert show];
} }
(void)connection:(NSURLConnection )connection didFailWithError:(NSError )error {
//DLog(@"%@", error.description);
[self cleanupConnection]; }
(void)credentialAlertSaved:(CredentialAlertView *)alert { self.username = alert.username; self.password = alert.password; [alert release];
[self play]; }
@end
Hope this helps... Antonijo
Thant makes perfect sense!
Thank you so much for taking the time to explain!
Madison
I need help. I have now found since updating my iphone 5 on ios 6, to ios 9, and mjpeg stream I play from my cameras will freeze after 5 seconds! It will not work for more than a few seconds on my iphone. I've tried 3 other iphones on ios9 and same thing. ANd I've tried 3 additional iphones on ios 6 and the stream never freezes! What gives?
Hello,
I am also seeing the freeze issue only under IOS6 where I did not have this problem before under IOS5. Has anyone found a work around? After days of Googling, I see some mjpeg streams have different formats that may be a cause...
To see the issue, please feel free to connect to my cameras mjpg stream at: http://admin:admin@24.51.192.24:213/cgi/mjpg/mjpg.cgi
You can see that Safari 6.0.2 also exhibits this issue so I am stuck trying to find a workaround...
Thanks you for any help you can offer, Madison