-
Notifications
You must be signed in to change notification settings - Fork 80
Seekable streams (Safari/iOS fix for video) #580
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
aa02e01
4ed4fb3
1b7e7c6
a804690
6a24e12
8624060
99a17c7
8198657
8a9ad8c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -63,24 +63,54 @@ public async Task<List<AlbumResponseDto>> GetAlbumInfo(Guid id, string clientIde | |
| [HttpGet("{id}/Image", Name = "GetImage")] | ||
| [Produces("image/jpeg", "image/webp")] | ||
| [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] | ||
| public async Task<FileResult> GetImage(Guid id, string clientIdentifier = "") | ||
| public async Task<IActionResult> GetImage(Guid id, string clientIdentifier = "") | ||
| { | ||
| return await GetAsset(id, clientIdentifier, AssetTypeEnum.IMAGE); | ||
| } | ||
|
|
||
| [HttpGet("{id}/Asset", Name = "GetAsset")] | ||
| [Produces("image/jpeg", "image/webp", "video/mp4", "video/quicktime")] | ||
| [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status200OK)] | ||
| public async Task<FileResult> GetAsset(Guid id, string clientIdentifier = "", AssetTypeEnum? assetType = null) | ||
| [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status206PartialContent)] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The 206 branch returns ♻️ Proposed fix- [ProducesResponseType(typeof(FileStreamResult), StatusCodes.Status206PartialContent)]
+ [ProducesResponseType(StatusCodes.Status206PartialContent)]🤖 Prompt for AI Agents |
||
| public async Task<IActionResult> GetAsset(Guid id, string clientIdentifier = "", AssetTypeEnum? assetType = null) | ||
| { | ||
| var sanitizedClientIdentifier = clientIdentifier.SanitizeString(); | ||
| _logger.LogDebug("Asset '{id}' requested by '{sanitizedClientIdentifier}' (type hint: {assetType})", id, sanitizedClientIdentifier, assetType); | ||
| var asset = await _logic.GetAsset(id, assetType); | ||
|
|
||
| var notification = new AssetRequestedNotification(id, sanitizedClientIdentifier); | ||
| _ = _logic.SendWebhookNotification(notification); | ||
| var rangeHeader = Request.Headers["Range"].FirstOrDefault(); | ||
| var asset = await _logic.GetAsset(id, assetType, rangeHeader); | ||
|
|
||
| if (string.IsNullOrEmpty(rangeHeader)) | ||
| { | ||
| var notification = new AssetRequestedNotification(id, sanitizedClientIdentifier); | ||
| _ = _logic.SendWebhookNotification(notification); | ||
| } | ||
|
|
||
| Response.Headers["Accept-Ranges"] = "bytes"; | ||
|
|
||
| if (asset.isPartial && !string.IsNullOrEmpty(asset.contentRange)) | ||
| { | ||
| Response.Headers["Content-Range"] = asset.contentRange; | ||
| Response.StatusCode = 206; | ||
| Response.ContentType = asset.ContentType; | ||
|
|
||
| return File(asset.fileStream, asset.ContentType, asset.fileName, enableRangeProcessing: true); | ||
| if (asset.fileStream is { CanSeek: true } && asset.fileStream.Length > 0) | ||
| Response.ContentLength = asset.fileStream.Length; | ||
| else if (!string.IsNullOrEmpty(asset.contentLength) && long.TryParse(asset.contentLength, out var length)) | ||
| Response.ContentLength = length; | ||
|
|
||
| await using (asset.fileStream) | ||
| { | ||
| await asset.fileStream.CopyToAsync(Response.Body); | ||
| } | ||
| asset.dispose?.Dispose(); | ||
| return new EmptyResult(); | ||
| } | ||
|
|
||
| using (asset.dispose) | ||
| { | ||
| return File(asset.fileStream, asset.ContentType, asset.fileName, enableRangeProcessing: true); | ||
| } | ||
|
Comment on lines
+102
to
+113
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's find and examine PooledImmichFrameLogic.cs to understand what dispose and fileStream are
find . -name "PooledImmichFrameLogic.cs" -type fRepository: immichFrame/ImmichFrame Length of output: 117 🏁 Script executed: # Search for GetImageAsset and GetVideoAsset method implementations
rg -n "GetImageAsset|GetVideoAsset" --type cs -A 15 | head -100Repository: immichFrame/ImmichFrame Length of output: 4247 🏁 Script executed: # Check AssetController.cs to see the full context around lines 102-113
fd "AssetController.cs" --type fRepository: immichFrame/ImmichFrame Length of output: 116 🏁 Script executed: # Read AssetController.cs to see the context around lines 102-113
cat -n ImmichFrame.WebApi/Controllers/AssetController.cs | head -150 | tail -60Repository: immichFrame/ImmichFrame Length of output: 3097 🏁 Script executed: # Get the complete GetImageAsset implementation
rg -n "private async Task.*GetImageAsset" ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs -A 40Repository: immichFrame/ImmichFrame Length of output: 1659 🏁 Script executed: # Get the complete GetVideoAsset implementation
rg -n "private async Task.*GetVideoAsset" ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs -A 80Repository: immichFrame/ImmichFrame Length of output: 1528 🏁 Script executed: # Find the GetAsset method that's called from the controller
rg -n "public.*Task.*GetAsset" ImmichFrame.Core/Logic/PooledImmichFrameLogic.cs -A 25Repository: immichFrame/ImmichFrame Length of output: 1777 🏁 Script executed: # Check if there are other usages of asset in the controller that might affect resource lifecycle
rg -n "asset\." ImmichFrame.WebApi/Controllers/AssetController.cs -B 2 -A 2 | head -80Repository: immichFrame/ImmichFrame Length of output: 1515 🏁 Script executed: # Verify what File() returns and search for documentation or usage patterns
rg -n "File\(" ImmichFrame.WebApi/Controllers/AssetController.cs -B 2 -A 2Repository: immichFrame/ImmichFrame Length of output: 269 Two resource-management bugs: Partial path (lines 102–107): Non-partial path (lines 110–113): For video assets, the 🐛 Proposed fix — partial path- await using (asset.fileStream)
- {
- await asset.fileStream.CopyToAsync(Response.Body);
- }
- asset.dispose?.Dispose();
+ try
+ {
+ await using (asset.fileStream)
+ {
+ await asset.fileStream.CopyToAsync(Response.Body);
+ }
+ }
+ finally
+ {
+ asset.dispose?.Dispose();
+ }🐛 Proposed fix — non-partial path- using (asset.dispose)
- {
- return File(asset.fileStream, asset.ContentType, asset.fileName, enableRangeProcessing: true);
- }
+ if (asset.dispose != null)
+ HttpContext.Response.RegisterForDispose(asset.dispose);
+ return File(asset.fileStream, asset.ContentType, asset.fileName, enableRangeProcessing: true);🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| [HttpGet("RandomImageAndInfo", Name = "GetRandomImageAndInfo")] | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.