using System.Text.Json;
using JiDangJia.Contracts;
using JiDangJia.Services;
namespace JiDangJia.Views;
public sealed class CommunityCapturePage : ContentPage
{
private readonly string kind,account=AuthClient.Current.AccountId;
private readonly Func<CommunityMedia,Task> send;
private readonly CommunityWebView web=new();
private readonly Label state=Ui.Text("准备录制",13,Ui.Muted);
private readonly MemoryStream data=new();
private readonly Button publish;
private string uploadId=Guid.NewGuid().ToString("N");
private CommunityMedia? uploaded;
private DateTimeOffset? createdAt;
private bool receiving,complete,busy,visible,paused,sent;
private bool CanUse=>visible&&!paused&&!AuthClient.Current.Locked&&account==AuthClient.Current.AccountId;
private Window? window;
public static async Task<bool> PermissionsFor(string kind)
{
if(await Permissions.RequestAsync<Permissions.Microphone>()!=PermissionStatus.Granted)throw new InvalidOperationException("录制语音和视频需要麦克风权限");
if(kind=="VIDEO"&&await Permissions.RequestAsync<Permissions.Camera>()!=PermissionStatus.Granted)throw new InvalidOperationException("视频功能需要相机权限");return true;
}
public static async Task Open(ContentPage page,string kind,Func<CommunityMedia,Task> send){await PermissionsFor(kind);await page.Navigation.PushAsync(new CommunityCapturePage(kind,send));}
private CommunityCapturePage(string kind,Func<CommunityMedia,Task> send)
{
this.kind=kind;this.send=send;Title=kind=="VIDEO"?"录制视频 · 最长30秒":"录制语音 · 最长60秒";publish=Ui.Button("发送录制内容",Publish);publish.IsEnabled=false;
var grid=CommunityUi.Layout(Ui.Stack(Ui.Text(kind=="VIDEO"?"在 APP 内录制视频":"在 APP 内录制语音",21,bold:true),state),web,Ui.Stack(publish,Ui.Button("取消录制",()=>Navigation.PopAsync(),false)));Content=grid;web.MessageReceived+=Message;
}
protected override async void OnAppearing()
{
base.OnAppearing();visible=true;paused=false;window=Window;if(window is not null){window.Stopped+=Stopped;window.Resumed+=Resumed;}await Prepare();
}
private async Task Prepare(){try{using var stream=await FileSystem.OpenAppPackageFileAsync("community/capture.html");using var reader=new StreamReader(stream);var html=(await reader.ReadToEndAsync()).Replace("__KIND__",kind);if(CanUse)web.Html=html;}catch(Exception ex){if(CanUse)state.Text=ex.Message;}}
private void Message(string value)
{
if(!CanUse||value=="ready")return;try
{
using var doc=JsonDocument.Parse(value);var r=doc.RootElement;switch(r.GetProperty("type").GetString())
{
case "status":state.Text=r.GetProperty("message").GetString();break;
case "reset":if(!busy){receiving=complete=false;uploaded=null;data.SetLength(0);uploadId=Guid.NewGuid().ToString("N");publish.IsEnabled=false;}break;
case "recorded":createdAt=r.TryGetProperty("createdAt",out var stamp)?stamp.GetDateTimeOffset():DateTimeOffset.Now;publish.IsEnabled=true;state.Text="录制完成,可以预览后发送。";break;
case "start":data.SetLength(0);receiving=true;complete=false;break;
case "chunk":if(!receiving)return;var bytes=Convert.FromBase64String(r.GetProperty("data").GetString()!);if(data.Length+bytes.Length>25*1024*1024){receiving=false;throw new ArgumentException("录制文件过大,请重新录制");}data.Write(bytes);break;
case "complete":receiving=false;complete=true;_=UploadAndSend();break;
}
}
catch(Exception ex)when(ex is JsonException or FormatException or ArgumentException){state.Text=ex.Message;publish.IsEnabled=true;}
}
private async Task Publish(){if(busy||!CanUse)return;await web.Run("window.setSending(true);");if(uploaded is not null||complete)await UploadAndSend();else{publish.IsEnabled=false;await web.Run("window.exportRecording();");}}
private async Task UploadAndSend()
{
if(busy||!CanUse)return;busy=true;publish.IsEnabled=false;
try{state.Text="正在发送录制内容…";uploaded??=await AuthClient.Current.UploadMedia(data.ToArray(),kind,uploadId,"CAMERA",createdAt,true);if(!CanUse)return;await send(uploaded);sent=true;if(CanUse&&Navigation.NavigationStack.LastOrDefault()==this)await Navigation.PopAsync();}
catch(Exception ex){if(visible){state.Text=ex.Message;publish.IsEnabled=true;await web.Run("window.setSending(false);");}}
finally{busy=false;if(CanUse&&!sent&&complete)publish.IsEnabled=true;}
}
private void Stopped(object? sender,EventArgs e){paused=true;web.Stop();state.Text="已暂停录制,请返回前台后重新录制。";publish.IsEnabled=false;receiving=false;if(!busy&&!complete)data.SetLength(0);}
private async void Resumed(object? sender,EventArgs e){paused=false;if(!CanUse)return;if(sent){if(Navigation.NavigationStack.LastOrDefault()==this)await Navigation.PopAsync();return;}await Prepare();publish.IsEnabled=!busy&&complete;state.Text=complete?"已保留录制内容,可以继续发送。":"请重新录制。";}
protected override void OnDisappearing(){visible=false;if(window is not null){window.Stopped-=Stopped;window.Resumed-=Resumed;}web.Stop();data.Dispose();base.OnDisappearing();}
}
using System.Text.Json;
using JiDangJia.Contracts;
using JiDangJia.Services;
namespace JiDangJia.Views;
public sealed class CommunityCapturePage : ContentPage
{
private readonly string kind,account=AuthClient.Current.AccountId;
private readonly Func<CommunityMedia,Task> send;
private readonly CommunityWebView web=new();
private readonly Label state=Ui.Text("准备录制",13,Ui.Muted);
private readonly MemoryStream data=new();
private readonly Button publish;
private string uploadId=Guid.NewGuid().ToString("N");
private CommunityMedia? uploaded;
private DateTimeOffset? createdAt;
private bool receiving,complete,busy,visible,paused,sent;
private bool CanUse=>visible&&!paused&&!AuthClient.Current.Locked&&account==AuthClient.Current.AccountId;
private Window? window;
public static async Task<bool> PermissionsFor(string kind)
{
if(await Permissions.RequestAsync<Permissions.Microphone>()!=PermissionStatus.Granted)throw new InvalidOperationException("录制语音和视频需要麦克风权限");
if(kind=="VIDEO"&&await Permissions.RequestAsync<Permissions.Camera>()!=PermissionStatus.Granted)throw new InvalidOperationException("视频功能需要相机权限");return true;
}
public static async Task Open(ContentPage page,string kind,Func<CommunityMedia,Task> send){await PermissionsFor(kind);await page.Navigation.PushAsync(new CommunityCapturePage(kind,send));}
private CommunityCapturePage(string kind,Func<CommunityMedia,Task> send)
{
this.kind=kind;this.send=send;Title=kind=="VIDEO"?"录制视频 · 最长30秒":"录制语音 · 最长60秒";publish=Ui.Button("发送录制内容",Publish);publish.IsEnabled=false;
var grid=CommunityUi.Layout(Ui.Stack(Ui.Text(kind=="VIDEO"?"在 APP 内录制视频":"在 APP 内录制语音",21,bold:true),state),web,Ui.Stack(publish,Ui.Button("取消录制",()=>Navigation.PopAsync(),false)));Content=grid;web.MessageReceived+=Message;
}
protected override async void OnAppearing()
{
base.OnAppearing();visible=true;paused=false;window=Window;if(window is not null){window.Stopped+=Stopped;window.Resumed+=Resumed;}await Prepare();
}
private async Task Prepare(){try{using var stream=await FileSystem.OpenAppPackageFileAsync("community/capture.html");using var reader=new StreamReader(stream);var html=(await reader.ReadToEndAsync()).Replace("__KIND__",kind);if(CanUse)web.Html=html;}catch(Exception ex){if(CanUse)state.Text=ex.Message;}}
private void Message(string value)
{
if(!CanUse||value=="ready")return;try
{
using var doc=JsonDocument.Parse(value);var r=doc.RootElement;switch(r.GetProperty("type").GetString())
{
case "status":state.Text=r.GetProperty("message").GetString();break;
case "reset":if(!busy){receiving=complete=false;uploaded=null;data.SetLength(0);uploadId=Guid.NewGuid().ToString("N");publish.IsEnabled=false;}break;
case "recorded":createdAt=r.TryGetProperty("createdAt",out var stamp)?stamp.GetDateTimeOffset():DateTimeOffset.Now;publish.IsEnabled=true;state.Text="录制完成,可以预览后发送。";break;
case "start":data.SetLength(0);receiving=true;complete=false;break;
case "chunk":if(!receiving)return;var bytes=Convert.FromBase64String(r.GetProperty("data").GetString()!);if(data.Length+bytes.Length>25*1024*1024){receiving=false;throw new ArgumentException("录制文件过大,请重新录制");}data.Write(bytes);break;
case "complete":receiving=false;complete=true;_=UploadAndSend();break;
}
}
catch(Exception ex)when(ex is JsonException or FormatException or ArgumentException){state.Text=ex.Message;publish.IsEnabled=true;}
}
private async Task Publish(){if(busy||!CanUse)return;await web.Run("window.setSending(true);");if(uploaded is not null||complete)await UploadAndSend();else{publish.IsEnabled=false;await web.Run("window.exportRecording();");}}
private async Task UploadAndSend()
{
if(busy||!CanUse)return;busy=true;publish.IsEnabled=false;
try{state.Text="正在发送录制内容…";uploaded??=await AuthClient.Current.UploadMedia(data.ToArray(),kind,uploadId,"CAMERA",createdAt,true);if(!CanUse)return;await send(uploaded);sent=true;if(CanUse&&Navigation.NavigationStack.LastOrDefault()==this)await Navigation.PopAsync();}
catch(Exception ex){if(visible){state.Text=ex.Message;publish.IsEnabled=true;await web.Run("window.setSending(false);");}}
finally{busy=false;if(CanUse&&!sent&&complete)publish.IsEnabled=true;}
}
private void Stopped(object? sender,EventArgs e){paused=true;web.Stop();state.Text="已暂停录制,请返回前台后重新录制。";publish.IsEnabled=false;receiving=false;if(!busy&&!complete)data.SetLength(0);}
private async void Resumed(object? sender,EventArgs e){paused=false;if(!CanUse)return;if(sent){if(Navigation.NavigationStack.LastOrDefault()==this)await Navigation.PopAsync();return;}await Prepare();publish.IsEnabled=!busy&&complete;state.Text=complete?"已保留录制内容,可以继续发送。":"请重新录制。";}
protected override void OnDisappearing(){visible=false;if(window is not null){window.Stopped-=Stopped;window.Resumed-=Resumed;}web.Stop();data.Dispose();base.OnDisappearing();}
}