fix(web): 恢复视频继续播放和进度保存

- 将播放器收敛为单个共享实例,避免列表/网格内多个 video ref 导致进度保存失效
- 恢复视频播放器上方的继续播放提示
- 在播放、暂停、拖动、结束、切换页面和离开页面时保存播放位置
- 保留文件浏览分页和排序参数的前端调用
This commit is contained in:
2026-05-23 11:03:51 +08:00
parent 27e4029f4a
commit c6b05c12e5
22 changed files with 2449 additions and 165 deletions
@@ -180,6 +180,7 @@ namespace FileShare_Services.Services.FileLibrary
record.Extension = info.Extension.ToLowerInvariant();
record.SizeBytes = info.Length;
record.LastWriteTimeUtc = info.LastWriteTimeUtc;
record.FileCreationTimeUtc = info.CreationTimeUtc;
record.MediaType = mediaType;
record.ContentType = contentType;
record.Exists = true;
@@ -334,9 +335,7 @@ namespace FileShare_Services.Services.FileLibrary
}
var total = await query.CountAsync(cancellationToken);
var items = await query
.OrderBy(file => file.MediaType)
.ThenBy(file => file.RelativePath)
var items = await ApplyFileSort(query, request.SortBy, request.SortDirection)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(file => ToFileDto(file))
@@ -359,6 +358,9 @@ namespace FileShare_Services.Services.FileLibrary
public async Task<BrowseDirectoryResponse> BrowseDirectoryAsync(BrowseDirectoryRequest request, CancellationToken cancellationToken = default)
{
var rootId = request.RootId;
var page = Math.Clamp(request.Page, 1, 100000);
var pageSize = Math.Clamp(request.PageSize, 1, 100);
var mediaType = request.MediaType?.Trim();
// URL 友好的正斜杠,用于响应和内存处理
var prefix = (request.Path ?? "").Trim().Replace('\\', '/').Trim('/');
// Windows 反斜杠,用于数据库查询
@@ -378,7 +380,7 @@ namespace FileShare_Services.Services.FileLibrary
var allFiles = await query.ToListAsync(cancellationToken);
var subdirs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var currentFiles = new List<FileRecordDto>();
var currentFiles = new List<ManagedFileRecord>();
foreach (var file in allFiles)
{
@@ -390,7 +392,7 @@ namespace FileShare_Services.Services.FileLibrary
var slashIndex = remaining.IndexOf('/');
if (slashIndex < 0)
{
currentFiles.Add(ToFileDto(file));
currentFiles.Add(file);
}
else
{
@@ -398,10 +400,28 @@ namespace FileShare_Services.Services.FileLibrary
}
}
if (!string.IsNullOrWhiteSpace(mediaType) && !mediaType.Equals("all", StringComparison.OrdinalIgnoreCase))
{
currentFiles = currentFiles
.Where(file => file.MediaType.Equals(mediaType, StringComparison.OrdinalIgnoreCase))
.ToList();
}
var total = currentFiles.Count;
var files = ApplyFileSort(currentFiles, request.SortBy, request.SortDirection)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(ToFileDto)
.ToList();
return new BrowseDirectoryResponse(
prefix,
subdirs.OrderBy(d => d, StringComparer.OrdinalIgnoreCase).ToList(),
currentFiles);
files,
total,
page,
pageSize,
pageSize > 0 ? (int)Math.Ceiling((double)total / pageSize) : 0);
}
/// <inheritdoc />
@@ -586,6 +606,76 @@ namespace FileShare_Services.Services.FileLibrary
return Math.Clamp(interval ?? DefaultScanIntervalMinutes, 1, 1440);
}
private static IQueryable<ManagedFileRecord> ApplyFileSort(
IQueryable<ManagedFileRecord> query,
string? sortBy,
string? sortDirection)
{
var descending = IsDescending(sortDirection);
return NormalizeSortBy(sortBy) switch
{
"size" => descending
? query.OrderByDescending(file => file.SizeBytes).ThenBy(file => file.FileName)
: query.OrderBy(file => file.SizeBytes).ThenBy(file => file.FileName),
"created" => descending
? query.OrderByDescending(file => file.FileCreationTimeUtc ?? file.LastWriteTimeUtc).ThenBy(file => file.FileName)
: query.OrderBy(file => file.FileCreationTimeUtc ?? file.LastWriteTimeUtc).ThenBy(file => file.FileName),
"modified" => descending
? query.OrderByDescending(file => file.LastWriteTimeUtc).ThenBy(file => file.FileName)
: query.OrderBy(file => file.LastWriteTimeUtc).ThenBy(file => file.FileName),
"type" => descending
? query.OrderByDescending(file => file.MediaType).ThenByDescending(file => file.Extension).ThenBy(file => file.FileName)
: query.OrderBy(file => file.MediaType).ThenBy(file => file.Extension).ThenBy(file => file.FileName),
_ => descending
? query.OrderByDescending(file => file.FileName).ThenBy(file => file.RelativePath)
: query.OrderBy(file => file.FileName).ThenBy(file => file.RelativePath),
};
}
private static IOrderedEnumerable<ManagedFileRecord> ApplyFileSort(
IEnumerable<ManagedFileRecord> files,
string? sortBy,
string? sortDirection)
{
var descending = IsDescending(sortDirection);
return NormalizeSortBy(sortBy) switch
{
"size" => descending
? files.OrderByDescending(file => file.SizeBytes).ThenBy(file => file.FileName)
: files.OrderBy(file => file.SizeBytes).ThenBy(file => file.FileName),
"created" => descending
? files.OrderByDescending(file => file.FileCreationTimeUtc ?? file.LastWriteTimeUtc).ThenBy(file => file.FileName)
: files.OrderBy(file => file.FileCreationTimeUtc ?? file.LastWriteTimeUtc).ThenBy(file => file.FileName),
"modified" => descending
? files.OrderByDescending(file => file.LastWriteTimeUtc).ThenBy(file => file.FileName)
: files.OrderBy(file => file.LastWriteTimeUtc).ThenBy(file => file.FileName),
"type" => descending
? files.OrderByDescending(file => file.MediaType).ThenByDescending(file => file.Extension).ThenBy(file => file.FileName)
: files.OrderBy(file => file.MediaType).ThenBy(file => file.Extension).ThenBy(file => file.FileName),
_ => descending
? files.OrderByDescending(file => file.FileName).ThenBy(file => file.RelativePath)
: files.OrderBy(file => file.FileName).ThenBy(file => file.RelativePath),
};
}
private static string NormalizeSortBy(string? sortBy)
{
return sortBy?.Trim().ToLowerInvariant() switch
{
"size" or "sizebytes" => "size",
"created" or "createdat" or "createtime" or "creationtime" => "created",
"modified" or "lastwrite" or "lastwritetime" or "lastwritetimeutc" or "time" or "date" => "modified",
"type" or "mediatype" or "extension" or "ext" => "type",
_ => "name",
};
}
private static bool IsDescending(string? sortDirection)
{
return sortDirection?.Trim().Equals("desc", StringComparison.OrdinalIgnoreCase) == true
|| sortDirection?.Trim().Equals("descending", StringComparison.OrdinalIgnoreCase) == true;
}
/// <summary>
/// 安全获取驱动器属性值,驱动器未就绪或访问异常时返回 null。
/// </summary>
@@ -641,6 +731,7 @@ namespace FileShare_Services.Services.FileLibrary
file.Extension,
file.SizeBytes,
file.LastWriteTimeUtc,
file.FileCreationTimeUtc,
file.MediaType,
file.ContentType,
$"/api/files/{file.Id}/stream",