[SOLVED] Assets with MP4 Video files not working using Azure on iOS (all browsers) or Safari (Desktop)

I have…

  • Read the following guideline: Troubleshooting and Support | Squidex. I understand that my support request might get deleted if I do not follow the guideline.
  • Used code blocks with ``` to format my code examples like JSON or logs properly.

I’m submitting a…

  • Regression (a behavior that stopped working in a new release)
  • Bug report
  • Performance issue
  • Documentation issue or request

Current behavior

Assets with MP4 video files are not working on Apple Desktop (Safari) or iPhone iOS (all browsers) when Squidex is installed using these docs:

or

I think the issue is happening because of the asset streaming error, but i am not sure. DownloadAsync might not be supported on azure storage for partial content.

Regarding this:

Azure Storage supports:
DownloadContent
DownloadContentAsync
DownloadStreaming
DownloadStreamingAsync
DownloadTo
DownloadToAsync

So i think the issue relies here where unsupported DownloadAsync Method is used:
backend/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs

Is there a quick fix for this or could you add support for Azure Storage on iOS?

Expected behavior

MP4 files should play on ios and in mac desktop (safari).

Minimal reproduction of the problem

Install Squidex to azure with storage account and test with iphone or mac.

Environment

App Name:

  • Self hosted with docker
  • Self hosted with IIS
  • Self hosted with other version
  • Cloud version

Version: master

Browser:

  • Chrome (desktop)
  • Chrome (Android)
  • Chrome (iOS)
  • Firefox
  • Safari (desktop)
  • Safari (iOS)
  • IE
  • Edge

Others:
I think also audio files has issues with partial content because of this same thing.

Which streaming error do you mean? Do you see something in the logs?

Btw: I use the streaming method: libs/assets/Squidex.Assets.Azure/AzureBlobAssetStore.cs at main · Squidex/libs · GitHub

The code you are point to in the AssetContentController does not use the azure client directly, but a wrapper that I have built to provide support for multiple storage options.

Do you know if the the mp4 work in Squidex Cloud? It is using Google Cloud storage.

Issue is not present on Google Cloud, only in Azure when Azure Storage is used. By streaming i mean that client automatically try to get assets with partial content and not by downloading them on one request. Partial content means range header and something goes wrong in range with iphone and mac. I think only way to fix it is to make fix to squidex code because it cannot be handled by forcing header values.

The error what Squidex gives when asset accessed via iphone:

Response Content-Length mismatch: too few bytes written

Or

Response Content-Length mismatch: too many bytes written

That’s weird. I have no iPhone at hand, right now. But I am forwarding the range header here:

If i give you iphone emulator access details to your email, could you try to solve this issue? If yes, to which email could i send them?

Here is two error examples what it produces:

2024-06-27T11:48:33.210051874Z “logLevel”: “Error”,
2024-06-27T11:48:33.210056574Z “message”: “Connection id \u00220HN4MHL5JUHBR\u0022, Request id \u00220HN4MHL5JUHBR:00000002\u0022: An unhandled exception was thrown by the application.”,
2024-06-27T11:48:33.210070974Z “name”: “ApplicationError”
2024-06-27T11:48:33.210075974Z },
2024-06-27T11:48:33.210122474Z },
2024-06-27T11:48:33.210126974Z “web”: {
2024-06-27T11:48:33.210131574Z “requestId”: “00-ed22bbec87c1b01a1ab8264bad63f9af-474ae5fcc2d1f783-01”,
2024-06-27T11:48:33.210136374Z “requestPath”: “/api/assets/1542379011/852a0e0c-cbf9-4f3a-bc3b-99e44589bb1b”,
2024-06-27T11:48:33.210140875Z “requestMethod”: “GET”
2024-06-27T11:48:33.210145375Z },
2024-06-27T11:48:33.210149575Z “category”: “Microsoft.AspNetCore.Server.Kestrel”,
2024-06-27T11:48:33.210164975Z “exception”: {
2024-06-27T11:48:33.210169775Z “type”: “System.InvalidOperationException”,
2024-06-27T11:48:33.210175275Z “message”: “Response Content-Length mismatch: too few bytes written (1 of 2).”
2024-06-27T11:48:33.210179975Z }
2024-06-27T11:48:33.210184375Z }

AND

2024-06-27T11:45:38.086692381Z {
2024-06-27T11:45:38.086745682Z “logLevel”: “Error”,
2024-06-27T11:45:38.086755482Z “message”: “An unexpected exception has occurred.”,
2024-06-27T11:45:38.086825483Z “requestMethod”: “GET”
2024-06-27T11:45:38.086831683Z },
2024-06-27T11:45:38.086837383Z “category”: “Squidex.Web.Pipeline.RequestExceptionMiddleware”,
2024-06-27T11:45:38.086843483Z “exception”: {
2024-06-27T11:45:38.086849283Z “type”: “System.InvalidOperationException”,
2024-06-27T11:45:38.086854383Z “message”: “Response Content-Length mismatch: too many bytes written (31799 of 16384).”,
2024-06-27T11:45:38.086865483Z “stackTrace”: " at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.WritePipeAsync(ReadOnlyMemory\u00601 data, CancellationToken cancellationToken)\n at Squidex.Web.Pipeline.UsageStream.WriteAsync(ReadOnlyMemory\u00601 buffer, CancellationToken cancellationToken) in /src/src/Squidex.Web/Pipeline/UsageStream.cs:line 80\n at System.IO.Stream.\u003CCopyToAsync\u003Eg__Core|27_0(Stream source, Stream destination, Int32 bufferSize, CancellationToken cancellationToken)\n at Squidex.Assets.AzureBlobAssetStore.DownloadAsync(String fileName, Stream stream, BytesRange range, CancellationToken ct)\n at Squidex.Assets.AzureBlobAssetStore.DownloadAsync(String fileName, Stream stream, BytesRange range, CancellationToken ct)\n at Squidex.Domain.Apps.Entities.Assets.DefaultAssetFileStore.DownloadAsync(DomainId appId, DomainId id, Int64 fileVersion, String suffix, Stream stream, BytesRange range, CancellationToken ct) in /src/src/Squidex.Domain.Apps.Entities/Assets/DefaultAssetFileStore.cs:line 81\n at Squidex.Areas.Api.Controllers.Assets.AssetContentController.DownloadAsync(Asset asset, Stream bodyStream, String suffix, BytesRange range, CancellationToken ct) in /src/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs:line 202\n at Squidex.Areas.Api.Controllers.Assets.AssetContentController.\u003C\u003Ec__DisplayClass8_0.\u003C\u003CDeliverAssetAsync\u003Eb__1\u003Ed.MoveNext() in /src/src/Squidex/Areas/Api/Controllers/Assets/AssetContentController.cs:line 184\n— End of stack trace from previous location —\n at Squidex.Web.Pipeline.FileCallbackResultExecutor.ExecuteAsync(ActionContext context, FileCallbackResult result) in /src/src/Squidex.Web/Pipeline/FileCallbackResultExecutor.cs:line 48\n at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.\u003CInvokeResultAsync\u003Eg__Logged|22_0(ResourceInvoker invoker, IActionResult result)\n at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.\u003CInvokeNextResultFilterAsync\u003Eg__Awaited|30_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\n at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)\n at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State\u0026 next, Scope\u0026 scope, Object\u0026 state, Boolean\u0026 isCompleted)\n at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.\u003CInvokeResultFilters\u003Eg__Awaited|28_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\n at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.\u003CInvokeNextResourceFilter\u003Eg__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\n at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)\n at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State\u0026 next, Scope\u0026 scope, Object\u0026 state, Boolean\u0026 isCompleted)\n at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.\u003CInvokeFilterPipelineAsync\u003Eg__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\n at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.\u003CInvokeAsync\u003Eg__Logged|17_1(ResourceInvoker invoker)\n at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.\u003CInvokeAsync\u003Eg__Logged|17_1(ResourceInvoker invoker)\n at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)\n at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)\n at Squidex.Web.Pipeline.UsageMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) in /src/src/Squidex.Web/Pipeline/UsageMiddleware.cs:line 39\n at Squidex.Web.Pipeline.UsageMiddleware.InvokeAsync(HttpContext context, RequestDelegate next) in /src/src/Squidex.Web/Pipeline/UsageMiddleware.cs:line 45\n at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.\u003C\u003Ec__DisplayClass2_0.\u003C\u003CCreateMiddleware\u003Eb__0\u003Ed.MoveNext()\n— End of stack trace from previous location —\n at Squidex.Web.Pipeline.RequestExceptionMiddleware.InvokeAsync(HttpContext context, IActionResultExecutor\u00601 writer, ILogger\u00601 log) in /src/src/Squidex.Web/Pipeline/RequestExceptionMiddleware.cs:line 42"

I think I have found the bug.

HttpRange from Azure is From + Length, not From + To

Great! When do you think you can push a fix to master branch?

Today…I will keep you updated.

I have pushed to master. It took way longer than expected because suddenly my computer started crashing when building the solution. I am still running mem check for the second time, but so far there is nothing. I guess I have to reset my computer.