|
1 | | -using System.Diagnostics.CodeAnalysis; |
| 1 | +using System.Diagnostics; |
| 2 | +using System.Diagnostics.CodeAnalysis; |
2 | 3 | using Android.Content; |
3 | 4 | using Android.Views; |
4 | 5 | using Android.Widget; |
@@ -536,27 +537,97 @@ protected override void Dispose(bool disposing) |
536 | 537 | } |
537 | 538 | } |
538 | 539 |
|
539 | | - static async Task<byte[]> GetBytesFromMetadataArtworkUrl(string? url, CancellationToken cancellationToken = default) |
| 540 | + static async Task<byte[]> GetBytesFromMetadataArtworkUrl(string url, CancellationToken cancellationToken = default) |
540 | 541 | { |
541 | | - byte[] artworkData = []; |
| 542 | + if (string.IsNullOrWhiteSpace(url)) |
| 543 | + { |
| 544 | + return []; |
| 545 | + } |
| 546 | + |
| 547 | + Stream? stream = null; |
| 548 | + Uri.TryCreate(url, UriKind.Absolute, out var uri); |
| 549 | + |
542 | 550 | try |
543 | 551 | { |
544 | | - var response = await client.GetAsync(url, cancellationToken).ConfigureAwait(false); |
545 | | - var stream = response.IsSuccessStatusCode ? await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false) : null; |
| 552 | + byte[] artworkData = []; |
| 553 | + long? contentLength = null; |
| 554 | + |
| 555 | + // HTTP or HTTPS URL |
| 556 | + if (uri is not null && |
| 557 | + (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)) |
| 558 | + { |
| 559 | + var request = new HttpRequestMessage(HttpMethod.Head, url); |
| 560 | + var contentLengthResponse = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); |
| 561 | + contentLength = contentLengthResponse.Content.Headers.ContentLength ?? 0; |
546 | 562 |
|
547 | | - if (stream is null) |
| 563 | + var response = await client.GetAsync(url, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false); |
| 564 | + stream = response.IsSuccessStatusCode ? await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false) : null; |
| 565 | + } |
| 566 | + // Absolute File Path |
| 567 | + else if (uri is not null && uri.Scheme == Uri.UriSchemeFile) |
548 | 568 | { |
549 | | - return artworkData; |
| 569 | + var normalizedFilePath = NormalizeFilePath(url); |
| 570 | + |
| 571 | + stream = File.Open(normalizedFilePath, FileMode.Create); |
| 572 | + contentLength = await GetByteCountFromStream(stream, cancellationToken); |
550 | 573 | } |
| 574 | + // Relative File Path |
| 575 | + else if (Uri.TryCreate(url, UriKind.Relative, out _)) |
| 576 | + { |
| 577 | + var normalizedFilePath = NormalizeFilePath(url); |
551 | 578 |
|
552 | | - using var memoryStream = new MemoryStream(); |
553 | | - await stream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false); |
554 | | - var bytes = memoryStream.ToArray(); |
555 | | - return bytes; |
| 579 | + stream = Platform.AppContext.Assets?.Open(normalizedFilePath) ?? throw new InvalidOperationException("Assets cannot be null"); |
| 580 | + contentLength = await GetByteCountFromStream(stream, cancellationToken); |
| 581 | + } |
| 582 | + |
| 583 | + if (stream is not null) |
| 584 | + { |
| 585 | + if (!contentLength.HasValue) |
| 586 | + { |
| 587 | + throw new InvalidOperationException($"{nameof(contentLength)} must be set when {nameof(stream)} is not null"); |
| 588 | + } |
| 589 | + |
| 590 | + artworkData = new byte[contentLength.Value]; |
| 591 | + using var memoryStream = new MemoryStream(artworkData); |
| 592 | + await stream.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false); |
| 593 | + } |
| 594 | + |
| 595 | + return artworkData; |
556 | 596 | } |
557 | | - catch |
| 597 | + catch (Exception e) |
558 | 598 | { |
559 | | - return artworkData; |
| 599 | + Trace.WriteLine($"Unable to retrieve {nameof(MediaElement.MetadataArtworkUrl)} for {url}.{e}\n"); |
| 600 | + return []; |
| 601 | + } |
| 602 | + finally |
| 603 | + { |
| 604 | + if (stream is not null) |
| 605 | + { |
| 606 | + stream.Close(); |
| 607 | + await stream.DisposeAsync(); |
| 608 | + } |
| 609 | + } |
| 610 | + |
| 611 | + static string NormalizeFilePath(string filePath) => filePath.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar); |
| 612 | + |
| 613 | + static async ValueTask<long> GetByteCountFromStream(Stream stream, CancellationToken token) |
| 614 | + { |
| 615 | + if (stream.CanSeek) |
| 616 | + { |
| 617 | + return stream.Length; |
| 618 | + } |
| 619 | + |
| 620 | + long countedStreamBytes = 0; |
| 621 | + |
| 622 | + var buffer = new byte[8192]; |
| 623 | + int bytesRead; |
| 624 | + |
| 625 | + while ((bytesRead = await stream.ReadAsync(buffer, token)) > 0) |
| 626 | + { |
| 627 | + countedStreamBytes += bytesRead; |
| 628 | + } |
| 629 | + |
| 630 | + return countedStreamBytes; |
560 | 631 | } |
561 | 632 | } |
562 | 633 |
|
@@ -636,7 +707,10 @@ void StopService(in BoundServiceConnection boundServiceConnection) |
636 | 707 | mediaMetaData.SetArtist(MediaElement.MetadataArtist); |
637 | 708 | mediaMetaData.SetTitle(MediaElement.MetadataTitle); |
638 | 709 | var data = await GetBytesFromMetadataArtworkUrl(MediaElement.MetadataArtworkUrl, cancellationToken).ConfigureAwait(true); |
639 | | - mediaMetaData.SetArtworkData(data, (Java.Lang.Integer)MediaMetadata.PictureTypeFrontCover); |
| 710 | + if (data is not null && data.Length > 0) |
| 711 | + { |
| 712 | + mediaMetaData.SetArtworkData(data, (Java.Lang.Integer)MediaMetadata.PictureTypeFrontCover); |
| 713 | + } |
640 | 714 |
|
641 | 715 | mediaItem = new MediaItem.Builder(); |
642 | 716 | mediaItem.SetUri(url); |
|
0 commit comments