2015-04-16 5 views
14

Sto utilizzando this approach per salvare i dati del buffer di AVPlayer per i file video. Trovato come risposta in questa domanda Saving buffer data of AVPlayer.AVPlayer in fase di arresto su file video di grandi dimensioni utilizzando il delegato del caricatore di risorse

iPhone e iPad - iOS 8.1.3

ho fatto i cambiamenti necessari per riprodurre video e si sta lavorando molto bene, tranne quando provo a giocare un lunghissimo video (lunga 11-12 minuti e circa 85MB in dimensione) il video si bloccherà circa 4 minuti dopo il completamento della connessione. Ricevo un evento per playbackBufferEmpty e una notifica di stallo di un elemento giocatore.

Questo è il senso del codice

viewController.m 
@property (nonatomic, strong) NSMutableData *videoData; 
@property (nonatomic, strong) NSURLConnection *connection; 
@property (nonatomic, strong) AVURLAsset *vidAsset; 
@property (nonatomic, strong) AVPlayerItem *playerItem; 
@property (nonatomic, strong) AVPlayerLayer *avlayer; 
@property (nonatomic, strong) NSHTTPURLResponse *response; 
@property (nonatomic, strong) NSMutableArray *pendingRequests; 


/** 
    Startup a Video 
*/ 
- (void)startVideo 
{ 
    self.vidAsset = [AVURLAsset URLAssetWithURL:[self videoURLWithCustomScheme:@"streaming"] options:nil]; 
    [self.vidAsset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()]; 
    self.pendingRequests = [NSMutableArray array]; 

    // Init Player Item 
    self.playerItem = [AVPlayerItem playerItemWithAsset:self.vidAsset]; 
    [self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:NULL]; 

    self.player = [[AVPlayer alloc] initWithPlayerItem:self.playerItem]; 

    // Init a video Layer 
    self.avlayer = [AVPlayerLayer playerLayerWithPlayer:self.player]; 
    [self.avlayer setFrame:self.view.frame]; 
    [self.view.layer addSublayer:self.avlayer]; 
} 

- (NSURL *)getRemoteVideoURL 
{ 
    NSString *urlString = [@"http://path/to/your/long.mp4"]; 
    return [NSURL URLWithString:urlString]; 
} 

- (NSURL *)videoURLWithCustomScheme:(NSString *)scheme 
{ 
    NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[self getRemoteVideoURL] resolvingAgainstBaseURL:NO]; 
    components.scheme = scheme; 
    return [components URL]; 
} 



/** 
    NSURLConnection Delegate Methods 
*/ 
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response 
{ 
    NSLog(@"didReceiveResponse"); 
    self.videoData = [NSMutableData data]; 
    self.response = (NSHTTPURLResponse *)response; 
    [self processPendingRequests]; 
} 

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 
{ 
    NSLog(@"Received Data - appending to video & processing request"); 
    [self.videoData appendData:data]; 
    [self processPendingRequests]; 
} 

- (void)connectionDidFinishLoading:(NSURLConnection *)connection 
{ 
    NSLog(@"connectionDidFinishLoading::WriteToFile"); 

    [self processPendingRequests]; 
    [self.videoData writeToFile:[self getVideoCachePath:self.vidSelected] atomically:YES]; 
} 


/** 
    AVURLAsset resource loader methods 
*/ 

- (void)processPendingRequests 
{ 
    NSMutableArray *requestsCompleted = [NSMutableArray array]; 

    for (AVAssetResourceLoadingRequest *loadingRequest in self.pendingRequests) 
    { 
     [self fillInContentInformation:loadingRequest.contentInformationRequest]; 

     BOOL didRespondCompletely = [self respondWithDataForRequest:loadingRequest.dataRequest]; 

     if (didRespondCompletely) 
     { 
      [requestsCompleted addObject:loadingRequest]; 

      [loadingRequest finishLoading]; 
     } 
    } 

    [self.pendingRequests removeObjectsInArray:requestsCompleted]; 
} 


- (void)fillInContentInformation:(AVAssetResourceLoadingContentInformationRequest *)contentInformationRequest 
{ 
    if (contentInformationRequest == nil || self.response == nil) 
    { 
     return; 
    } 

    NSString *mimeType = [self.response MIMEType]; 
    CFStringRef contentType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef)(mimeType), NULL); 

    contentInformationRequest.byteRangeAccessSupported = YES; 
    contentInformationRequest.contentType = CFBridgingRelease(contentType); 
    contentInformationRequest.contentLength = [self.response expectedContentLength]; 
} 


- (BOOL)respondWithDataForRequest:(AVAssetResourceLoadingDataRequest *)dataRequest 
{ 
    long long startOffset = dataRequest.requestedOffset; 
    if (dataRequest.currentOffset != 0) 
    { 
     startOffset = dataRequest.currentOffset; 
    } 

    // Don't have any data at all for this request 
    if (self.videoData.length < startOffset) 
    { 
     NSLog(@"NO DATA FOR REQUEST"); 
     return NO; 
    } 

    // This is the total data we have from startOffset to whatever has been downloaded so far 
    NSUInteger unreadBytes = self.videoData.length - (NSUInteger)startOffset; 

    // Respond with whatever is available if we can't satisfy the request fully yet 
    NSUInteger numberOfBytesToRespondWith = MIN((NSUInteger)dataRequest.requestedLength, unreadBytes); 

    [dataRequest respondWithData:[self.videoData subdataWithRange:NSMakeRange((NSUInteger)startOffset, numberOfBytesToRespondWith)]]; 

    long long endOffset = startOffset + dataRequest.requestedLength; 
    BOOL didRespondFully = self.videoData.length >= endOffset; 

    return didRespondFully; 
} 

- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest 
{ 
    if (self.connection == nil) 
    { 
     NSURL *interceptedURL = [loadingRequest.request URL]; 
     NSURLComponents *actualURLComponents = [[NSURLComponents alloc] initWithURL:interceptedURL resolvingAgainstBaseURL:NO]; 
     actualURLComponents.scheme = @"http"; 

     NSURLRequest *request = [NSURLRequest requestWithURL:[actualURLComponents URL]]; 
     self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; 
     [self.connection setDelegateQueue:[NSOperationQueue mainQueue]]; 

     [self.connection start]; 
    } 

    [self.pendingRequests addObject:loadingRequest]; 

    return YES; 
} 

- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest 
{ 
    NSLog(@"didCancelLoadingRequest"); 
    [self.pendingRequests removeObject:loadingRequest]; 
} 


/** 
    KVO 
*/ 

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 
{ 
    if (context == StatusObservationContext) 
{ 
    AVPlayerStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue]; 

    if (status == AVPlayerStatusReadyToPlay) { 
     [self initHud]; 
     [self play:NO]; 
    } else if (status == AVPlayerStatusFailed) 
    { 
     NSLog(@"ERROR::AVPlayerStatusFailed"); 

    } else if (status == AVPlayerItemStatusUnknown) 
    { 
     NSLog(@"ERROR::AVPlayerItemStatusUnknown"); 
    } 

} else if (context == CurrentItemObservationContext) { 


} else if (context == RateObservationContext) { 


} else if (context == BufferObservationContext){ 


} else if (context == playbackLikelyToKeepUp) { 

    if (self.player.currentItem.playbackLikelyToKeepUp) 


    } 

} else if (context == playbackBufferEmpty) { 

    if (self.player.currentItem.playbackBufferEmpty) 
    { 
     NSLog(@"Video Asset is playable: %d", self.videoAsset.isPlayable); 

     NSLog(@"Player Item Status: %ld", self.player.currentItem.status); 

     NSLog(@"Connection Request: %@", self.connection.currentRequest); 

     NSLog(@"Video Data: %lu", (unsigned long)self.videoData.length); 


    } 

} else if(context == playbackBufferFull) { 


} else { 

    [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 
} 

} 

Il problema sembra essere che qualche tempo dopo il collegamento termine del caricamento, il buffer elemento giocatore va vuoto. Il mio pensiero al momento è che qualcosa viene rilasciato quando la connessione finisce di caricare e rovinare il buffer playerItem.

Tuttavia al momento il buffer va vuoto lo stato playerItem è buona, la risorsa video è giocabile, i dati video è buona

Se dell'acceleratore il wifi attraverso charles e rallentare il collegamento, il video viene riprodotto fino a quando la connessione non termina il caricamento entro pochi minuti dalla fine del video.

Se imposto la connessione nil sull'evento di caricamento completato, il caricatore di risorse attiverà una nuova connessione quando shouldWaitForLoadingOfRequestedResource verrà nuovamente attivato. In questo caso, il caricamento ricomincia da capo e il video continuerà a essere riprodotto.

Devo dire che questo video lungo funziona bene se lo riproduco come un normale asset URL http, e suona bene dopo essere stato salvato sul dispositivo e caricato da lì.

risposta

8

quando il delegato del caricatore di risorse attiva NSURLConnection, la connessione esegue il salvataggio degli NSData alle richieste in sospeso e all'elaborazione. al termine del caricamento della connessione, il caricatore di risorse riprende la responsabilità della gestione delle richieste di caricamento. il codice stava aggiungendo la richiesta di caricamento alla matrice delle richieste in sospeso ma il problema era che non venivano elaborati. cambiato il metodo al seguente e funziona.

//AVAssetResourceLoader 
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest 
{ 
    if(isLoadingComplete == YES) 
    { 
     //NSLog(@"LOADING WAS COMPLETE"); 
     [self.pendingRequests addObject:loadingRequest]; 
     [self processPendingRequests]; 
     return YES; 
    } 

    if (self.connection == nil) 
    { 
     NSURL *interceptedURL = [loadingRequest.request URL]; 
     NSURLComponents *actualURLComponents = [[NSURLComponents alloc] initWithURL:interceptedURL resolvingAgainstBaseURL:NO]; 
     actualURLComponents.scheme = @"http"; 
     self.request = [NSURLRequest requestWithURL:[actualURLComponents URL]]; 
     self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; 
     [self.connection setDelegateQueue:[NSOperationQueue mainQueue]]; 

     isLoadingComplete = NO; 
     [self.connection start]; 
    } 

    [self.pendingRequests addObject:loadingRequest]; 
    return YES; 
} 
+0

Ciao Paul, Sto cercando di far avviare i video il più presto possibile. Ma sembra che io sia in grado di riprodurre il video solo dopo che è stato scaricato completamente. Ho provato il tuo codice e l'approccio originale per i file mp3. Ma l'AVPlayer non li sta riproducendo fino a quando non è completamente scaricato. Qualche suggerimento/pensiero? – Nikolozi

+1

che tipo di video stai cercando di riprodurre? Stiamo usando i file progressivi h.264 mp4. Potresti dare un'occhiata anche alle tue intestazioni. questo potrebbe aiutare - https://transcoding.wordpress.com/2012/09/22/making-a--2-264-for-the-web-stream-instantly/ –

+0

Cheers per la risposta Paul. È risultato che stavo usando un file video di test non valido. Stavo usando un video casuale da un server casuale. Il codice ha funzionato bene con i video sui nostri server. – Nikolozi

Problemi correlati