/// <summary> /// CreateManifestCache /// Create a manifest cache. /// /// </summary> /// <param name="manifestUri">manifest Uri</param> /// <param name="downloadToGo">true if Download To Go experience, Progressive Downlaoad experience otherwise</param> /// <param name="audioIndex">Index of the video track to select </param> /// <param name="audioIndex">Index of the audio track to select (usually 0)</param> /// <param name="maxMemoryBufferSize">maximum memory buffer size</param> /// <param name="maxError">maximum http error while downloading the chunks</param> /// <param name="downloadMethod"> 0 Auto: The cache will create if necessary several threads to download audio and video chunks /// 1 Default: The cache will download the audio and video chunks step by step in one single thread /// N The cache will create N parallel threads to download the audio chunks and N parallel threads to downlaod video chunks </param> /// <returns>return a ManifestCache (null if not successfull)</returns> public static ManifestCache CreateManifestCache(Uri manifestUri, bool downloadToGo, int videoIndex, int audioIndex, ulong maxMemoryBufferSize, uint maxError, int downloadMethod = 1) { // load the stream associated with the HLS, SMOOTH or DASH manifest ManifestCache mc = new ManifestCache(manifestUri, downloadToGo, videoIndex, audioIndex, maxMemoryBufferSize, maxError, downloadMethod); if (mc != null) { mc.ManifestStatus = AssetStatus.Initialized; } return mc; }
/// <summary> /// RemoveAsset /// Remove the asset on disk: audio chunks, video chunks and manifest /// </summary> public async Task<bool> RemoveAsset(ManifestCache cache) { bool bResult = false; await RemoveAudioChunks(cache); await RemoveVideoChunks(cache); await RemoveManifest(cache); bResult = await RemoveDirectory(Path.Combine(root, cache.StoragePath)); return bResult; }
/// <summary> /// SaveAsset /// Save the asset on disk: audio chunks, video chunks and manifest /// </summary> public async Task<bool> SaveAsset(ManifestCache cache) { bool bResult = true; if (!(await SaveAudioChunks(cache))) { bResult = false; System.Diagnostics.Debug.WriteLine(string.Format("{0:d/M/yyyy HH:mm:ss.fff}", DateTime.Now) + " Error while saving audio chunks for url: " + cache.ManifestUri.ToString()); } if (!(await SaveVideoChunks(cache))) { bResult = false; System.Diagnostics.Debug.WriteLine(string.Format("{0:d/M/yyyy HH:mm:ss.fff}", DateTime.Now) + " Error while saving video chunks for url: " + cache.ManifestUri.ToString()); } if (!(await SaveManifest(cache))) { bResult = false; System.Diagnostics.Debug.WriteLine(string.Format("{0:d/M/yyyy HH:mm:ss.fff}", DateTime.Now) + " Error while saving manifest chunks for url: " + cache.ManifestUri.ToString()); } return bResult; }
/// <summary> /// GetAssetSize /// Return the current asset size on disk: audio chunks, video chunks and manifest /// </summary> public async Task<ulong> GetAssetSize(ManifestCache cache) { ulong val = 0; string path = string.Empty; path = Path.Combine(Path.Combine(root, cache.StoragePath), manifestFileName); if (!string.IsNullOrEmpty(path)) val += await GetFileSize(path); using (var releaser = await internalVideoDiskLock.ReaderLockAsync()) { path = Path.Combine(Path.Combine(root, cache.StoragePath), videoIndexFileName); if (!string.IsNullOrEmpty(path)) val += await GetFileSize(path); path = Path.Combine(Path.Combine(root, cache.StoragePath), videoContentFileName); if (!string.IsNullOrEmpty(path)) val += await GetFileSize(path); } using (var releaser = await internalAudioDiskLock.ReaderLockAsync()) { path = Path.Combine(Path.Combine(root, cache.StoragePath), audioIndexFileName); if (!string.IsNullOrEmpty(path)) val += await GetFileSize(path); path = Path.Combine(Path.Combine(root, cache.StoragePath), audioContentFileName); if (!string.IsNullOrEmpty(path)) val += await GetFileSize(path); } return val; }
/// <summary> /// RemoveManifest /// Remove manifest from disk /// </summary> public async Task<bool> RemoveManifest(ManifestCache cache) { bool bResult = false; string path = Path.Combine(Path.Combine(root, cache.StoragePath), manifestFileName); if (path != null) { bool res = await FileExists(path); if (res) bResult = await DeleteFile(path); } return bResult; }
/// <summary> /// RemoveVideoChunks /// Remove video chunks from disk /// </summary> public async Task<bool> RemoveVideoChunks(ManifestCache cache) { bool bResult = false; string pathContent = Path.Combine(Path.Combine(root, cache.StoragePath), videoContentFileName); string pathIndex = Path.Combine(Path.Combine(root, cache.StoragePath), videoIndexFileName); using (var releaser = await internalVideoDiskLock.WriterLockAsync()) { if (pathContent != null) { bool res = await FileExists(pathContent); if (res) bResult = await DeleteFile(pathContent); } if (pathIndex != null) { bool res = await FileExists(pathIndex); if (res) bResult = await DeleteFile(pathIndex); } } return bResult; }
/// <summary> /// SaveVideoChunks /// Save video chunks on disk /// </summary> public async Task<bool> SaveVideoChunks(ManifestCache cache) { bool bResult = false; string VideoIndexFile = Path.Combine(Path.Combine(root, cache.StoragePath), videoIndexFileName); string VideoContentFile = Path.Combine(Path.Combine(root, cache.StoragePath), videoContentFileName); if ((!string.IsNullOrEmpty(VideoIndexFile)) && (!string.IsNullOrEmpty(VideoContentFile))) { using (var releaser = await internalVideoDiskLock.WriterLockAsync()) { ulong VideoOffset = await GetFileSize(VideoContentFile); ulong InitialVideoOffset = VideoOffset; // delete the initial files /* await DeleteFile(VideoIndexFile); await DeleteFile(VideoContentFile); cache.VideoSavedChunks = 0; */ for (int Index = (int)cache.VideoSavedChunks; Index < (int)cache.VideoDownloadedChunks; Index++) //foreach (var cc in cache.VideoChunkList) { var cc = cache.VideoChunkList[Index]; if ((cc != null) && (cc.GetLength() > 0)) { IndexCache ic = new IndexCache(cc.Time, VideoOffset, cc.GetLength()); if (ic != null) { ulong res = await Append(VideoContentFile, cc.chunkBuffer); if (res == cc.GetLength()) { VideoOffset += res; ulong result = await Append(VideoIndexFile, ic.GetByteData()); if (result == indexSize) { cache.VideoSavedChunks++; cache.VideoSavedBytes += res; // free buffer cc.chunkBuffer = null; // cache.VideoChunkList[Index].chunkBuffer = null; } } } } else break; } if (InitialVideoOffset < VideoOffset) bResult = true; if (cache.VideoSavedChunks == cache.VideoDownloadedChunks) bResult = true; } } return bResult; }
/// <summary> /// SaveManifest /// Save manifest on disk /// </summary> public async Task<bool> SaveManifest(ManifestCache cache) { bool bResult = false; using (var releaser = await internalManifestDiskLock.WriterLockAsync()) { System.Diagnostics.Debug.WriteLine(string.Format("{0:d/M/yyyy HH:mm:ss.fff}", DateTime.Now) + " internalManifestDiskLock Writer Enter for Uri: " + cache.ManifestUri.ToString()); try { using (MemoryStream ms = new MemoryStream()) { if (ms != null) { System.Runtime.Serialization.DataContractSerializer ser = new System.Runtime.Serialization.DataContractSerializer(typeof(ManifestCache)); ser.WriteObject(ms, cache); bResult = await Save(Path.Combine(Path.Combine(root, cache.StoragePath), manifestFileName), ms.ToArray()); } } } catch(Exception e) { System.Diagnostics.Debug.WriteLine(string.Format("{0:d/M/yyyy HH:mm:ss.fff}", DateTime.Now) + " internalManifestDiskLock Writer exception for Uri: " + cache.ManifestUri.ToString() + " Exception: " + e.Message); } System.Diagnostics.Debug.WriteLine(string.Format("{0:d/M/yyyy HH:mm:ss.fff}", DateTime.Now) + " internalManifestDiskLock Writer Exit for Uri: " + cache.ManifestUri.ToString()); } return bResult; }