2010-11-18 11 views
19

Czy ktoś wie, czy AVQueuePlayer rozpoczyna buforowanie następnego AVPlayerItem, gdy bieżący element ma się zakończyć?Wstępne buforowanie AVQueue Player

Wiem, że w dokumentach nie ma niczego, co mogłoby to zasugerować. Pytam głównie, czy ktoś zaobserwował takie zachowanie, czy nie.

Odpowiedz

20

Ok, przejrzałem ten problem ponownie i napisałem kod, aby sprawdzić AVQueuePlayer.

Odpowiedź jollyCocoa wskazała mi właściwy kierunek, sugerując obserwację właściwości statusu na AVPlayerItem. Jednak dokumentacja nie wydaje się wskazywać, że ta właściwość (aw szczególności jej wartość to AVPlayerItemStatusReadyToPlay) może być związana z buforowaniem.

Właściwość wydaje się jednak bardziej związana z buforowaniem.

Wykonywanie KVO na tej macierzy było nieco trudniejsze - sam obiekt tablicy się nie zmienia, robią to tylko jego elementy - dlatego uciekałem się do drukowania zawartości na sekundę.

Co dowiedziałem się, że kilka sekund w pierwszym elemencie kolejki, loadedTimeRanges dla drugiego elementu pokazuje nowy CMTimeRange z czasem rozpoczęcia 0 i krótkim czasem trwania. Czas trwania może wzrosnąć do 60 sekund, podczas gdy poprzedni element odtwarza.

Krótka odpowiedź:AVQueuePlayer buforuje następny AVPlayerItem podczas odtwarzania bieżącego.

+0

Ładowanie elementu AVPlayerItem nie powoduje opóźnień odtwarzacza; jeśli się nie zgadzasz pokaż mi kod, który to udowodni. Mam działającą aplikację, która ładuje jeden plik AVPlayerItem dla każdego zasobu wideo na telefonie iPhone w jednej komórce widoku kolekcji. Możesz przewijać tak szybko, jak chcesz, a pierwsza klatka każdego filmu będzie wyświetlana tak, jakbyś prosił tylko o pojedynczy obraz z zasobu. Jednak opóźnienie odtwarzania wydaje się WYŁĄCZNIE, jeśli zaczyna się podczas przewijania. To dlatego, że umieszczasz zbyt wiele rzeczy w jednym wątku i jednej kolejce. Rozpocznij odtwarzanie w oddzielnym wątku i kolejce, a opóźnienie zniknie. –

2

Eksperymentowałem z AVQueuePlayer przy użyciu filmów dostarczonych przez serwer. W tym konkretnym teście ustawiłem program queuePlayer z AVPlayerItems zainicjowanym za pomocą URL na dwa różne zasoby wideo różnych typów. Jednym z nich jest plik .mp4 (pobieranie progresywne), a drugi plik przesyłania strumieniowego http .m3u8. Brzmi nieźle, muszę powiedzieć.

W zależności od kolejności kolejkowania plików otrzymuję zupełnie inne zachowanie. Jeśli zacznę od pliku .mp4, warstwa AVPlayerLayer staje się pusta i cicha, gdy nextItem uruchamia odtwarzacz (plik .m3u8). Jeśli zmienię kolejność i zacznę od pliku m3u8, AVPlayerLayer zniknie, ale dźwięk z drugiego pliku będzie dobrze odtwarzany.

Moje wrażenia z tego, co widziałem do tej pory, to to, że AVQueuePlayer robi NOT rozpocząć pobieranie następnego AVPlayerItem przed zakończeniem odtwarzania bieżącego AVPlayerItem. Ale mogę się mylić.

Aby być kontynuowane Chyba ...

Edit: Ok, więc zrobiłem trochę więcej badań i wydaje się następne pozycje rozpoczyna pobieranie przed ostatnia pozycja zakończeniu odtwarzania. Zobacz post poniżej. Skok "NIE" ...

Edycja2: Dodałem tutaj moje wyjaśnienie, ponieważ komentarz pozostawił mnie bez opcji formatowania. (Best praktyką jest to mile widziane, jeśli jest to zły sposób obsłużyć aktualizacje odpowiedź)

Tak czy inaczej, ja powtórzyć za moim itemsArray dodając obserwacji stanu mienia przy użyciu KVO ...

[playerItem addObserver: self forKeyPath:@"status" options: 0 context: NULL]; 

ja również ustawić mój kontroler jako obserwatora od zawiadomienia AVPlayerItem AVPlayerItemDidPlayToEndTimeNotification:

[[NSNotificationCenter defaultCenter] addObserver: self 
             selector: @selector(playerItemDidReachEnd:) 
              name: AVPlayerItemDidPlayToEndTimeNotification 
              object: nil]; 

teraz mogę zalogować zmianę statusu AVPlayerItem, a okazuje się, że w następnym AVPlayerItem t kolejka jest rejestrowana ze zmianą statusu AVPlayerItemStatusReadyToPlay przed zakończeniem odtwarzania bieżącego elementu.

Mój wniosek jest taki, że następny element w wierszu rozpoczyna pobieranie przed zakończeniem bieżącego elementu.

Jednak!Tworzę tablicę z elementami AVPlayerItems, których używam do tworzenia mojego odtwarzacza. Odczytując dokumenty AVFoundation na AVAssets, mam wrażenie, że te asynchronicznie pobierają swoje zasoby, ale wciąż nie jestem pewien, kiedy te pobrania są inicjowane przez IAPlayerItem. Nadal mam pewne funkowe zachowanie, gdy dodam plik .m3u8 do kolejki, co sprawia, że ​​zastanawiam się, czy te zasoby zaczną się pobierać w momencie tworzenia (wydaje mi się to jednak nieco dziwne).

+0

Ok, więc przeglądam elementy w tablicy przedmiotów, dodając obserwację za pośrednictwem KVO ... Ten komentarz nie pozwala na formatowanie. Zamiast tego edytuję mój główny wpis! – jollyCocoa

6

Od wersji iOS 5 wygląda na to, że AVQueuePlayer nie jest już wstępnie buforowany. Nastąpiło buforowanie następnego utworu w iOS 4.

Nie jestem pewien, dlaczego nastąpiła zmiana lub jeśli jest to nawet celowe ze strony Apple lub po prostu błąd. W każdym razie, to jest ból, a ja zgłoszę im na ten temat błąd.

+0

Jeśli przesłałeś raport o błędzie, możesz go otworzyć, abyśmy mogli go skopiować? –

+0

Czy były jakieś postępy związane z tym raportem o błędzie? Moja aplikacja ma ten sam błąd, kolejkowanie dwóch przechowywanych lokalnie plików mp4 powoduje nieprzyjemne zatrzymanie odtwarzania, gdy AVQueuePlayer przejdzie do następnego pliku. –

+0

@ matt-gallagher, czy słyszałeś kiedyś od Apple o braku wstępnego buforowania w AVQueuePlayer? –

5

Znaleziono pracę! Po wielu godzinach wyszukiwania i nieudanych testach znalazłem rozwiązanie działające na iOS 5 i nowszych wersjach.

Spowoduje to odtworzenie plików bez żadnych opóźnień. Nie jest to wygodne, jeśli chcesz przełączać się między plikami, ale na pewno wszystko połączysz za Ciebie. To wciąż możliwe, aby śledzić, gdzie rozpoczyna się każdy plik, podczas dodawania AVURLAsset zachować zmienną CMTime, coś takiego:

NSValue *toJumpTo = [NSValue valueWithCMTime:composition.duration]; 
[array addObject:toJumpTo]; 

a potem po prostu przejść przez tablicę, sprawdzenie aktualnej wartości czasu i porównanie go z użyciem CMTimeCompare.

Edytuj: Znaleziono na http://www.developers-life.com/avplayer-looping-video-without-hiccupdelays.html, ale jak wspomniał Stefan Kendall w komentarzach, jest to obecnie witryna ze spamem. Pomyślałem, że zostawię link do celów atrybucji, ale nie odwiedzaj go!

+1

Och, ta sztuczka AVComposition była DOKŁADNIE tym, czego potrzebowałem. Twoje zdrowie! :) – Goz

1

Zrobiłem proste demo dla Ciebie i innych, którzy chcą skrócić czas buforowania każdego filmu z adresu URL.

UWAGA: Nie wiem, to jest właściwa droga, czy nie, ale to praca idealna dla mnie bez problemu. Jeśli ktoś wie więcej lub chce poprawić kod w celu uzyskania lepszych wyników, zapraszamy do zmiany odpowiedzi.

W poniższym kodzie Pierwsze wideo wykonuje normalny czas buforowania. Następny film rozpocznie się automatycznie po zakończeniu bieżącego wideo i możesz przesuwać palcem w lewo lub w prawo, aby przejść do następnego i poprzedniego wideo.

Wykonaj poniższe czynności.

1) W pliku wideoPlayerVC.h.

#import <AVFoundation/AVFoundation.h> 
#import <AVKit/AVKit.h> 

@interface videoPlayerVC : UIViewController 
{ 
    AVPlayerViewController *avPlyrViewController; 
    AVPlayerItem *currentAVPlyrItem; 

    int currentVideoNO; // For current video track. 
    NSMutableArray *arrOfAVPItems; 
    NSMutableArray *arrOfPlyer; 
} 

2) W videoPlayerVC.m pliku

Tutaj można rozpocząć odtwarzanie wideo przez kliknięcie przycisku. Poniżej znajduje się metoda działania przycisku.

-(void)clickOnPlayVideosImage:(UIButton *)sender 
{ 
    // In my App. all video URLs is up to 15 second. 
    currentVideoNO = 0; 
    NSMutableArray *arrForVideoURL = [[NSMutableArray alloc]initWithObjects: 
             [NSURL URLWithString:@"http://videos/url1.mov"], 
             [NSURL URLWithString:@"http://videos/url2.mov"], 
             [NSURL URLWithString:@"http://videos/url3.mov"], 
             [NSURL URLWithString:@"http://videos/url3.mov"], 
             [NSURL URLWithString:@"http://videos/url4.mov"], 
             [NSURL URLWithString:@"http://videos/url5.mov"], nil]; 

    AVPlayerItem *thePlayerItemA = [[AVPlayerItem alloc] initWithURL:[arrForVideoURL objectAtIndex:0]]; 
    AVPlayerItem *thePlayerItemB = [[AVPlayerItem alloc] initWithURL:[arrForVideoURL objectAtIndex:1]]; 
    AVPlayerItem *thePlayerItemC = [[AVPlayerItem alloc] initWithURL:[arrForVideoURL objectAtIndex:2]]; 
    AVPlayerItem *thePlayerItemD = [[AVPlayerItem alloc] initWithURL:[arrForVideoURL objectAtIndex:3]]; 
    AVPlayerItem *thePlayerItemE = [[AVPlayerItem alloc] initWithURL:[arrForVideoURL objectAtIndex:4]]; 
    AVPlayerItem *thePlayerItemF = [[AVPlayerItem alloc] initWithURL:[arrForVideoURL objectAtIndex:5]]; 

    if(arrOfAVPItems.count > 0) 
     [arrOfAVPItems removeAllObjects]; 
    arrOfAVPItems = nil; 

    if(arrOfPlyer.count > 0) 
     [arrOfPlyer removeAllObjects]; 
    arrOfPlyer = nil; 
    arrOfPlyer = [[NSMutableArray alloc] init]; 

    arrOfAVPItems = [NSMutableArray arrayWithObjects:thePlayerItemA, thePlayerItemB, thePlayerItemC, thePlayerItemD, thePlayerItemE, thePlayerItemF, nil]; // Add All items in the Array 

    for(AVPlayerItem *myPlyrItem in arrOfAVPItems) 
    { 
     AVPlayer *videoPlayerNext = [AVPlayer playerWithPlayerItem:myPlyrItem]; /// Add item in the player 
     [videoPlayerNext play]; 
     [videoPlayerNext pause]; 
     [arrOfPlyer addObject:videoPlayerNext]; /// Make Array of "AVPlayer" just reduce buffering of each video. 
    } 

    avPlyrViewController = [AVPlayerViewController new]; 
    avPlyrViewController.delegate = self; 
    avPlyrViewController.player = (AVPlayer *)arrOfPlyer[0]; // Add first player from the Array. 
    avPlyrViewController.showsPlaybackControls = YES; 
    avPlyrViewController.allowsPictureInPicturePlayback = YES; 
    avPlyrViewController.videoGravity = AVLayerVideoGravityResizeAspect; 

    [self presentViewController:avPlyrViewController animated:YES completion:^{ 
     [self performSelector:@selector(playVideos) withObject:nil afterDelay:1];/// call method after one second for play video. 

     UISwipeGestureRecognizer * swipeleft=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeleftToNextVideo:)]; 
     swipeleft.direction=UISwipeGestureRecognizerDirectionLeft; 
     [avPlyrViewController.view addGestureRecognizer:swipeleft]; // Add left swipe for move on next video 

     UISwipeGestureRecognizer * swiperight=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipeleftToPerviousVideo:)]; 
     swiperight.direction=UISwipeGestureRecognizerDirectionRight; 
     [avPlyrViewController.view addGestureRecognizer:swiperight]; // Add right swipe for move on previous video 
    }]; 
} 

Teraz wpisz kod metody playVideos.

#pragma mark - AVPlayer Methods - 

-(void)playVideos 
{ 
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:currentAVPlyrItem]; // remove current "AVPlayerItem" from the notification observe. 

    if(arrOfAVPItems.count > 0) 
    { 
     currentAVPlyrItem = (AVPlayerItem *)arrOfAVPItems[currentVideoNO]; // Add "AVPlayerItem" from the array. 

     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:currentAVPlyrItem]; // Add notification observer to indication current "AVPlayerItem" is finish. 

     // pause and nil previous player if available. 
     [avPlyrViewController.player pause]; 
     avPlyrViewController.player = nil; 

     avPlyrViewController.player = (AVPlayer *)arrOfPlyer[currentVideoNO]; // add new player from the array 
     [avPlyrViewController.player.currentItem seekToTime:kCMTimeZero]; // set for start video on initial position. 
     [avPlyrViewController.player play]; // Play video 

    } 
} 

Obserwator powiadomień o zakończeniu wideo.

- (void)playerItemDidReachEnd:(NSNotification *)notification 
{ 
    NSLog(@"IT REACHED THE END"); 
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:currentAVPlyrItem]; // remove current "AVPlayerItem" from the notification observe. 

    [self swipeleftToNextVideo:nil]; // Call method for next video. 
} 

Metoda Play Następny wideo

-(void)swipeleftToNextVideo:(UISwipeGestureRecognizer*)gestureRecognizer 
{ 
    currentVideoNO++; 
    if(currentVideoNO > (arrOfAVPItems.count -1)) 
     currentVideoNO = 0; 

    NSLog(@"current - %d and last - %d", currentVideoNO, (int)(arrOfAVPItems.count -1)); 

    [self playVideos]; 
} 

metoda odtwarzania poprzedniego filmu.

-(void)swipeleftToPerviousVideo:(UISwipeGestureRecognizer*)gestureRecognizer 
{ 
    currentVideoNO--; 
    if(currentVideoNO < 0) 
     currentVideoNO = (int)(arrOfAVPItems.count -1); 

    NSLog(@"current - %d and last - %d", currentVideoNO, (int)(arrOfAVPItems.count -1)); 

    [self playVideos]; 
} 

Wykonaj następujące kroki. W razie wątpliwości prosimy o komentarz.

+0

czy możesz podać przykład z powyższym kodem dla avqueueplayer –