14

iOS 7 を使用しており、アプリにダウンロードする必要がある .mp4 ビデオがあります。ビデオはサイズが大きい (~ 1 GB) ため、アプリの一部として含まれていません。ダウンロードが開始されるとすぐに、ユーザーがビデオの視聴を開始できるようにしたいと考えています。また、ユーザーが後で再度ダウンロードする必要がないように、ビデオを iOS デバイスにキャッシュできるようにしたいと考えています。ビデオを再生する通常の方法 (プログレッシブ ダウンロードとライブ ストリーミング) の両方で、ビデオをキャッシュできないようです。そのため、ビデオ ファイルをチャンクアップしてバイトをクライアントにストリーミングする独自の Web サービスを作成しました。NSURLConnection を使用してストリーミング HTTP 呼び出しを開始します。

self.request = [[NSMutableURLRequest alloc] initWithURL:self.url];
[self.request setTimeoutInterval:10]; // Expect data at least every 10 seconds
[self.request setHTTPMethod:@"GET"];
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:YES];

データ チャンクを受信したら、ファイルのローカル コピーの末尾に追加します。

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
     NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:[self videoFilePath]];
     [handle truncateFileAtOffset:[handle seekToEndOfFile]];
     [handle writeData:data];
}

デバイスを実行すると、ファイルが正常にダウンロードされ、MPMoviePlayerViewController を使用して再生できます。

NSURL *url=[NSURL fileURLWithPath:self.videoFilePath];
MPMoviePlayerViewController *controller = [[MPMoviePlayerViewController alloc] initWithContentURL:url];
controller.moviePlayer.scalingMode = MPMovieScalingModeAspectFit;
[self presentMoviePlayerViewControllerAnimated:controller];

ただし、ファイルが完全にダウンロードされる前にプレーヤーを起動すると、ビデオは問題なく再生されます。上部のスクラバー バーに正しいビデオの長さが表示されます。しかし、ユーザーがビデオの開始前にダウンロードを完了したビデオの位置に到達すると、ビデオがハングします。MPMoviePlayerViewController を閉じて再度開くと、MPMoviePlayerViewController を再度起動したときの場所に到達するまでビデオが再生されます。ビデオ全体がダウンロードされるまで待つと、ビデオは問題なく再生されます。

これが発生したときにイベントが発生したり、エラー メッセージがコンソールに出力されたりしません (ビデオの開始後に MPMoviePlayerPlaybackStateDidChangeNotification と MPMoviePlayerPlaybackDidFinishNotification が送信されることはありません)。スクラバーが使用しているもの以外に、ビデオの長さをコントローラーに伝えているものが他にあるようです...

この問題の原因を知っている人はいますか? 私は MPMoviePlayerViewController を使用することに縛られていないので、この状況で別のビデオ再生方法が機能する場合は、それで十分です。

関連する未解決の質問:

AVURLAssets を使用した AVPlayer およびプログレッシブ ビデオ ダウンロード

iOS でのプログレッシブ ビデオ ダウンロード

IOS でダウンロード中のビデオ ファイルを再生する方法

更新 1 ビデオの再生が開始されたときのファイル サイズが原因で、実際にビデオが停止することがわかりました。この問題は、ダウンロードを開始する前にゼロ化されたファイルを作成し、途中で上書きすることで回避できます。私はビデオ ストリーミング サーバーを制御できるので、カスタム ヘッダーを追加して、ストリーミングされるファイルのサイズがわかるようにしました (ストリーミング ファイルのデフォルトのファイル サイズ ヘッダーは -1 です)。次のように、didReceiveResponse メソッドでファイルを作成しています。

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{        
    // Retrieve the size of the file being streamed.
    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
    NSDictionary *headers = httpResponse.allHeaderFields;

    NSNumberFormatter * formatter = [[NSNumberFormatter alloc] init];
    [formatter setNumberStyle:NSNumberFormatterDecimalStyle];
    self.streamingFileSize = [formatter numberFromString:[headers objectForKey:@"StreamingFileSize"]];

    // Check if we need to initialize the download file
    if (![[NSFileManager defaultManager] fileExistsAtPath:self.path])
    {            
        // Create the file being downloaded
        [[NSData data] writeToFile:self.path atomically:YES];

        // Allocate the size of the file we are going to download.
        const char *cString = [self.path cStringUsingEncoding:NSASCIIStringEncoding];
        int success = truncate(cString, self.streamingFileSize.longLongValue);
        if (success != 0)
        {
            /* TODO: handle errors here. Probably not enough space... See 'man truncate' */
        }
    }
}

これはうまく機能しますが、トランケートによってディスク上に ~1GB のファイルが作成される間、アプリが約 10 秒間ハングすることを除きます (シミュレーターでは瞬時に発生し、実際のデバイスでのみこの問題が発生します)。これは私が今行き詰まっている場所です-ファイルをより効率的に割り当てる方法、または実際に割り当てる必要なしにビデオプレーヤーにファイルのサイズを認識させる別の方法を知っている人はいますか? 一部のファイルシステムは「ファイルサイズ」と「ディスク上のサイズ」を2つの異なるプロパティとしてサポートしていることを知っています... iOSにそのようなものがあるかどうかわかりませんか?

4

3 に答える 3

12

私はこれを行う方法を考え出しました。それは私の最初のアイデアよりもはるかに簡単です。

まず、ビデオは .mp4 であるため、MPMoviePlayerViewController または AVPlayer クラスは Web サーバーから直接再生できます。特別なものを実装する必要はなく、ビデオの任意のポイントをシークできます。これは、.mp4 エンコーディングがムービー プレーヤーでどのように機能するかの一部である必要があります。そのため、サーバーで生のファイルを利用できるだけです。特別なヘッダーは必要ありません。

次に、ユーザーがビデオの再生を決定すると、すぐにサーバー URL からビデオの再生を開始します。

NSURL *url=[NSURL fileURLWithPath:serverVidelFileURLString];
controller = [[MPMoviePlayerViewController alloc] initWithContentURL:url];
controller.moviePlayer.scalingMode = MPMovieScalingModeAspectFit;
[self presentMoviePlayerViewControllerAnimated:controller];

これにより、ユーザーはビデオを見て、好きな場所を探すことができます。次に、上記と同じように NSURLConnection を使用して手動でファイルのダウンロードを開始します。ただし、ファイルをストリーミングするのではなく、直接ダウンロードするだけです。この方法では、ファイル サイズが HTTP 応答に含まれているため、カスタム ヘッダーは必要ありません。

バックグラウンド ダウンロードが完了したら、再生中のアイテムをサーバー URL からローカル ファイルに切り替えます。これは、ネットワーク パフォーマンスにとって重要です。ムービー プレーヤーは、ユーザーが見ているものの数秒前にダウンロードするだけだからです。できるだけ早くローカル ファイルに切り替えることができることが、重複データのダウンロードを回避するための鍵となります。

NSTimeInterval currentPlaybackTime = videoController.moviePlayer.currentPlaybackTime;

[controller.moviePlayer setContentURL:url];
[controller.moviePlayer setCurrentPlaybackTime:currentPlaybackTime];
[controller.moviePlayer play];

この方法では、ユーザーは最初に 2 つのビデオ ファイルを同時にダウンロードしますが、ユーザーが使用するネットワーク速度の初期テストでは、ダウンロード時間が数秒しか長くならないことが示されています。私のために働く!

于 2014-01-08T19:30:59.137 に答える