arcane-os 0.48.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/README.md +1 -1
- package/browser-runtime/ai/browser-speech-artifacts.mjs +4 -0
- package/browser-runtime/ai/browser-speech-providers.mjs +1 -1
- package/browser-runtime/ai/browser-speech-synthesis.mjs +568 -0
- package/browser-runtime/ai/browser-speech.mjs +1 -0
- package/browser-runtime/ai/twin-cloud-speech.mjs +148 -55
- package/docs/reference/ai/browser-speech.md +97 -12
- package/docs/reference/runtime-modules.md +34 -5
- package/package.json +1 -1
- package/runtime/arcane/components/speech.html +11 -4
- package/runtime/arcane/modules/AI.js +179 -17
- package/runtime/arcane/modules/SpeechPlayback.js +242 -36
|
@@ -239,6 +239,10 @@ async function playableSpeechBlob(response){
|
|
|
239
239
|
throw synthesizedAudioContractError();
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
function isNativeSpeechPlayback(value){
|
|
243
|
+
return value?.kind==='native-speech'&&is.function(value.play);
|
|
244
|
+
}
|
|
245
|
+
|
|
242
246
|
function speechPlaybackFailureReason(error){
|
|
243
247
|
if(error?.name==='AbortError')return 'speech-synthesis-cancelled';
|
|
244
248
|
if(is.string(error?.code)
|
|
@@ -310,7 +314,7 @@ function queueFor(speech){
|
|
|
310
314
|
}
|
|
311
315
|
|
|
312
316
|
function providerSpeechCapacity(speech){
|
|
313
|
-
if(!is.function(speech?.fetchTTS))return null;
|
|
317
|
+
if(!is.function(speech?.prepareTTSPlayback)&&!is.function(speech?.fetchTTS))return null;
|
|
314
318
|
try{
|
|
315
319
|
const providerRuntime=speech.providerRuntime;
|
|
316
320
|
if(!providerRuntime||!is.function(providerRuntime.status))return null;
|
|
@@ -330,7 +334,7 @@ function startProviderSegment(playback,index,providerSynthesisBatch){
|
|
|
330
334
|
providerSynthesisBatch!==playback.providerSynthesisBatch
|
|
331
335
|
||index<0
|
|
332
336
|
||index>=playback.parts.length
|
|
333
|
-
||playback.
|
|
337
|
+
||playback.hasSegment(index)
|
|
334
338
|
)return null;
|
|
335
339
|
const existing=playback.segmentRequests.get(index);
|
|
336
340
|
if(existing?.providerSynthesisBatch===providerSynthesisBatch){
|
|
@@ -348,10 +352,10 @@ function startProviderSegment(playback,index,providerSynthesisBatch){
|
|
|
348
352
|
function storeProviderSynthesizedSegment(outcome){
|
|
349
353
|
if(outcome===SUPERSEDED)return {ready:false,superseded:true};
|
|
350
354
|
if(providerSynthesisBatch!==playback.providerSynthesisBatch){
|
|
351
|
-
playback.
|
|
355
|
+
playback.discardSegment(outcome);
|
|
352
356
|
return {ready:false,superseded:true};
|
|
353
357
|
}
|
|
354
|
-
playback.
|
|
358
|
+
playback.retainSegment(index,outcome);
|
|
355
359
|
return {ready:true};
|
|
356
360
|
},
|
|
357
361
|
function handleProviderSegmentFailure(error){
|
|
@@ -401,6 +405,7 @@ const DEFAULT_MESSAGES={
|
|
|
401
405
|
autoplayBlocked:'Speech is ready. Browser autoplay was blocked; press Play.',
|
|
402
406
|
playing:'Reading the visible content aloud.',
|
|
403
407
|
buffering:'Preparing the next speech segment.',
|
|
408
|
+
waitingForVoices:'Waiting for the browser voice list.',
|
|
404
409
|
paused:'Narration paused.',
|
|
405
410
|
pausing:'Pausing between narration sections.',
|
|
406
411
|
stopped:'Speech stopped. Start narration again to replay.',
|
|
@@ -464,6 +469,8 @@ class SpeechPlayback{
|
|
|
464
469
|
};
|
|
465
470
|
this.messages={...DEFAULT_MESSAGES,...messages};
|
|
466
471
|
this.urls=[];
|
|
472
|
+
this.nativeSegments=new Map();
|
|
473
|
+
this.nativePlayback=null;
|
|
467
474
|
this.parts=[];
|
|
468
475
|
this.index=0;
|
|
469
476
|
this.generation=0;
|
|
@@ -519,9 +526,9 @@ class SpeechPlayback{
|
|
|
519
526
|
message,
|
|
520
527
|
key,
|
|
521
528
|
index:this.index,
|
|
522
|
-
total:this.parts.length||this.urls.filter(Boolean).length,
|
|
529
|
+
total:this.parts.length||this.urls.filter(Boolean).length+this.nativeSegments.size,
|
|
523
530
|
producing:this.synthesisInFlight,
|
|
524
|
-
buffered:this.urls.filter(Boolean).length,
|
|
531
|
+
buffered:this.urls.filter(Boolean).length+this.nativeSegments.size,
|
|
525
532
|
hasAudio:this.hasAudio(),
|
|
526
533
|
operationId:this.operationId,
|
|
527
534
|
code,
|
|
@@ -551,6 +558,8 @@ class SpeechPlayback{
|
|
|
551
558
|
this.speech
|
|
552
559
|
&&(
|
|
553
560
|
is.function(this.speech.fetchTTS)
|
|
561
|
+
||is.function(this.speech.prepareTTSPlayback)
|
|
562
|
+
||is.function(this.speech.prepare)
|
|
554
563
|
||is.function(this.speech.synthesize)
|
|
555
564
|
)
|
|
556
565
|
);
|
|
@@ -558,22 +567,38 @@ class SpeechPlayback{
|
|
|
558
567
|
|
|
559
568
|
hasAudio(key=this.key){
|
|
560
569
|
const playable=this.providerSynthesisBatch
|
|
561
|
-
?
|
|
562
|
-
:this.urls.some(Boolean);
|
|
570
|
+
?this.hasSegment(0)
|
|
571
|
+
:this.urls.some(Boolean)||this.nativeSegments.size>0;
|
|
563
572
|
return Boolean(playable&&(!key||key===this.key));
|
|
564
573
|
}
|
|
565
574
|
|
|
575
|
+
hasSegment(index){
|
|
576
|
+
return Boolean(this.urls[index])||this.nativeSegments.has(index);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
retainSegment(index,outcome){
|
|
580
|
+
if(outcome.nativeSpeech)this.nativeSegments.set(index,outcome.nativeSpeech);
|
|
581
|
+
else this.urls[index]=outcome.url;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
discardSegment(outcome){
|
|
585
|
+
if(outcome?.url)this.revokeObjectURL(outcome.url);
|
|
586
|
+
}
|
|
587
|
+
|
|
566
588
|
releaseURLs(){
|
|
589
|
+
const stopped=this.stopNativePlayback();
|
|
567
590
|
for(const url of this.urls){
|
|
568
591
|
if(url)this.revokeObjectURL(url);
|
|
569
592
|
}
|
|
570
593
|
this.urls=[];
|
|
594
|
+
this.nativeSegments.clear();
|
|
571
595
|
this.parts=[];
|
|
572
596
|
this.lookahead=null;
|
|
573
597
|
this.lookaheadError=null;
|
|
574
598
|
this.providerSynthesisBatch=null;
|
|
575
599
|
this.segmentRequests.clear();
|
|
576
600
|
this.segmentErrors.clear();
|
|
601
|
+
return stopped;
|
|
577
602
|
}
|
|
578
603
|
|
|
579
604
|
releaseUrls(){this.releaseURLs();}
|
|
@@ -590,10 +615,10 @@ class SpeechPlayback{
|
|
|
590
615
|
this.audio.removeAttribute?.('src');
|
|
591
616
|
this.audio.load?.();
|
|
592
617
|
this.audio.hidden=true;
|
|
593
|
-
this.releaseURLs();
|
|
618
|
+
const stopped=this.releaseURLs();
|
|
594
619
|
this.index=0;
|
|
595
620
|
this.key='';
|
|
596
|
-
this.emit('idle',message,cancelledKey,{reason});
|
|
621
|
+
if(stopped)this.emit('idle',message,cancelledKey,{reason});
|
|
597
622
|
}
|
|
598
623
|
|
|
599
624
|
async requestSpeech(part,signal){
|
|
@@ -606,11 +631,19 @@ class SpeechPlayback{
|
|
|
606
631
|
...(part.voice?{voice:part.voice}:{}),
|
|
607
632
|
...(this.responseFormat?{responseFormat:this.responseFormat}:{})
|
|
608
633
|
};
|
|
634
|
+
if(is.function(this.speech?.prepareTTSPlayback)){
|
|
635
|
+
const prepared=await this.speech.prepareTTSPlayback(payload,signal,preparation);
|
|
636
|
+
return isNativeSpeechPlayback(prepared)?prepared:playableSpeechBlob(prepared);
|
|
637
|
+
}
|
|
609
638
|
if(is.function(this.speech?.fetchTTS)){
|
|
610
639
|
return playableSpeechBlob(
|
|
611
640
|
await this.speech.fetchTTS(payload,signal,preparation)
|
|
612
641
|
);
|
|
613
642
|
}
|
|
643
|
+
if(is.function(this.speech?.prepare)){
|
|
644
|
+
const prepared=await this.speech.prepare(payload,{signal},preparation);
|
|
645
|
+
return isNativeSpeechPlayback(prepared)?prepared:playableSpeechBlob(prepared);
|
|
646
|
+
}
|
|
614
647
|
if(is.function(this.speech?.synthesize)){
|
|
615
648
|
return playableSpeechBlob(
|
|
616
649
|
await this.speech.synthesize(payload,{signal},preparation)
|
|
@@ -647,7 +680,7 @@ class SpeechPlayback{
|
|
|
647
680
|
if(announce){
|
|
648
681
|
playback.emit('synthesizing',playback.message('preparing',{count:playback.parts.length}));
|
|
649
682
|
}
|
|
650
|
-
const
|
|
683
|
+
const prepared=await playback.requestSpeech(
|
|
651
684
|
part,
|
|
652
685
|
controller.signal
|
|
653
686
|
);
|
|
@@ -655,7 +688,8 @@ class SpeechPlayback{
|
|
|
655
688
|
controller.abort();
|
|
656
689
|
return SUPERSEDED;
|
|
657
690
|
}
|
|
658
|
-
|
|
691
|
+
if(isNativeSpeechPlayback(prepared))return {nativeSpeech:prepared};
|
|
692
|
+
const url=playback.createObjectURL(prepared);
|
|
659
693
|
if(!segmentRequestIsCurrent()){
|
|
660
694
|
playback.revokeObjectURL(url);
|
|
661
695
|
controller.abort();
|
|
@@ -681,7 +715,7 @@ class SpeechPlayback{
|
|
|
681
715
|
generation!==this.generation
|
|
682
716
|
||index<0
|
|
683
717
|
||index>=this.parts.length
|
|
684
|
-
||this.
|
|
718
|
+
||this.hasSegment(index)
|
|
685
719
|
)return null;
|
|
686
720
|
if(
|
|
687
721
|
this.lookahead
|
|
@@ -695,10 +729,10 @@ class SpeechPlayback{
|
|
|
695
729
|
function storeSynthesizedSegment(outcome){
|
|
696
730
|
if(outcome===SUPERSEDED)return {ready:false,superseded:true};
|
|
697
731
|
if(generation!==playback.generation){
|
|
698
|
-
playback.
|
|
732
|
+
playback.discardSegment(outcome);
|
|
699
733
|
return {ready:false,superseded:true};
|
|
700
734
|
}
|
|
701
|
-
playback.
|
|
735
|
+
playback.retainSegment(index,outcome);
|
|
702
736
|
return {ready:true};
|
|
703
737
|
},
|
|
704
738
|
function handleLookaheadFailure(error){
|
|
@@ -716,19 +750,19 @@ class SpeechPlayback{
|
|
|
716
750
|
|
|
717
751
|
async waitForSegment(index,generation){
|
|
718
752
|
if(generation!==this.generation)return false;
|
|
719
|
-
if(this.
|
|
753
|
+
if(this.hasSegment(index))return true;
|
|
720
754
|
if(this.providerSynthesisBatch){
|
|
721
755
|
const segmentError=this.segmentErrors.get(index);
|
|
722
756
|
if(segmentError?.providerSynthesisBatch===this.providerSynthesisBatch){
|
|
723
757
|
return false;
|
|
724
758
|
}
|
|
725
759
|
const record=this.segmentRequests.get(index);
|
|
726
|
-
if(!record)return
|
|
760
|
+
if(!record)return this.hasSegment(index);
|
|
727
761
|
const result=await record.promise;
|
|
728
762
|
return generation===this.generation
|
|
729
763
|
&&record.providerSynthesisBatch===this.providerSynthesisBatch
|
|
730
764
|
&&result.ready===true
|
|
731
|
-
&&
|
|
765
|
+
&&this.hasSegment(index);
|
|
732
766
|
}
|
|
733
767
|
if(
|
|
734
768
|
this.lookaheadError
|
|
@@ -740,9 +774,9 @@ class SpeechPlayback{
|
|
|
740
774
|
&&this.lookahead.generation===generation
|
|
741
775
|
&&this.lookahead.index===index
|
|
742
776
|
)?this.lookahead:this.startLookahead(index,generation);
|
|
743
|
-
if(!record)return
|
|
777
|
+
if(!record)return this.hasSegment(index);
|
|
744
778
|
const result=await record.promise;
|
|
745
|
-
return generation===this.generation&&result.ready===true&&
|
|
779
|
+
return generation===this.generation&&result.ready===true&&this.hasSegment(index);
|
|
746
780
|
}
|
|
747
781
|
|
|
748
782
|
async waitForPause(duration,generation){
|
|
@@ -836,7 +870,11 @@ class SpeechPlayback{
|
|
|
836
870
|
);
|
|
837
871
|
const result=await first.promise;
|
|
838
872
|
if(Object.hasOwn(result,'error'))throw result.error;
|
|
839
|
-
outcome=result.ready
|
|
873
|
+
outcome=result.ready
|
|
874
|
+
?this.nativeSegments.has(0)
|
|
875
|
+
?{nativeSpeech:this.nativeSegments.get(0)}
|
|
876
|
+
:{url:this.urls[0]}
|
|
877
|
+
:SUPERSEDED;
|
|
840
878
|
}
|
|
841
879
|
}catch(error){
|
|
842
880
|
if(generation!==this.generation)return {ready:false,played:false};
|
|
@@ -847,7 +885,7 @@ class SpeechPlayback{
|
|
|
847
885
|
throw error;
|
|
848
886
|
}
|
|
849
887
|
if(outcome===SUPERSEDED||generation!==this.generation){
|
|
850
|
-
|
|
888
|
+
this.discardSegment(outcome);
|
|
851
889
|
if(generation===this.generation){
|
|
852
890
|
this.releaseURLs();
|
|
853
891
|
this.key='';
|
|
@@ -856,12 +894,11 @@ class SpeechPlayback{
|
|
|
856
894
|
return {ready:false,played:false};
|
|
857
895
|
}
|
|
858
896
|
|
|
859
|
-
this.
|
|
897
|
+
this.retainSegment(0,outcome);
|
|
860
898
|
this.index=0;
|
|
861
|
-
this.
|
|
862
|
-
this.
|
|
863
|
-
this.
|
|
864
|
-
const played=autoplay?await this.play():false;
|
|
899
|
+
const loaded=this.loadCurrent();
|
|
900
|
+
if(loaded)this.emit('ready',this.message('ready'));
|
|
901
|
+
const played=autoplay&&loaded?await this.play():false;
|
|
865
902
|
if(generation===this.generation&&!this.providerSynthesisBatch){
|
|
866
903
|
this.startLookahead(1,generation);
|
|
867
904
|
}
|
|
@@ -869,13 +906,26 @@ class SpeechPlayback{
|
|
|
869
906
|
}
|
|
870
907
|
|
|
871
908
|
loadCurrent(){
|
|
872
|
-
if(!this.
|
|
909
|
+
if(!this.hasSegment(this.index))return false;
|
|
910
|
+
if(this.nativePlayback&&!this.stopNativePlayback())return false;
|
|
911
|
+
if(this.nativeSegments.has(this.index)){
|
|
912
|
+
this.audio.pause();
|
|
913
|
+
this.audio.removeAttribute?.('src');
|
|
914
|
+
this.audio.load?.();
|
|
915
|
+
this.audio.hidden=true;
|
|
916
|
+
return true;
|
|
917
|
+
}
|
|
918
|
+
this.audio.hidden=false;
|
|
873
919
|
this.audio.src=this.urls[this.index];
|
|
874
920
|
this.audio.load?.();
|
|
921
|
+
return true;
|
|
875
922
|
}
|
|
876
923
|
|
|
877
924
|
async play(){
|
|
878
925
|
if(!this.hasAudio())return false;
|
|
926
|
+
if(this.nativeSegments.has(this.index))return this.playNative();
|
|
927
|
+
if(this.nativePlayback&&!this.stopNativePlayback())return false;
|
|
928
|
+
if(this.audio.src!==this.urls[this.index]&&!this.loadCurrent())return false;
|
|
879
929
|
try{
|
|
880
930
|
await this.audio.play();
|
|
881
931
|
return true;
|
|
@@ -890,17 +940,156 @@ class SpeechPlayback{
|
|
|
890
940
|
}
|
|
891
941
|
}
|
|
892
942
|
|
|
943
|
+
stopNativePlayback(){
|
|
944
|
+
const active=this.nativePlayback;
|
|
945
|
+
if(!active)return true;
|
|
946
|
+
active.cancelled=true;
|
|
947
|
+
let stopped=false;
|
|
948
|
+
let failure=null;
|
|
949
|
+
try{
|
|
950
|
+
stopped=active.control?.stop()===true;
|
|
951
|
+
}catch(error){
|
|
952
|
+
failure=error;
|
|
953
|
+
}finally{
|
|
954
|
+
active.controller.abort();
|
|
955
|
+
}
|
|
956
|
+
const released=active.released||!active.control||stopped
|
|
957
|
+
||active.control.state==='complete'||active.control.state==='stopped';
|
|
958
|
+
if(!released){
|
|
959
|
+
failure=failure??active.control.error;
|
|
960
|
+
if(failure){
|
|
961
|
+
active.failed=true;
|
|
962
|
+
console.error('[Arcane speech playback] Native speech stop failed.',failure);
|
|
963
|
+
this.fail(failure);
|
|
964
|
+
}
|
|
965
|
+
return false;
|
|
966
|
+
}
|
|
967
|
+
if(this.nativePlayback===active)this.nativePlayback=null;
|
|
968
|
+
this.abortControllers.delete(active.controller);
|
|
969
|
+
return true;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
playNative(){
|
|
973
|
+
const prepared=this.nativeSegments.get(this.index);
|
|
974
|
+
if(!prepared)return false;
|
|
975
|
+
const previous=this.nativePlayback;
|
|
976
|
+
if(previous&&previous.generation===this.generation&&previous.index===this.index){
|
|
977
|
+
try{
|
|
978
|
+
if(previous.failed)return false;
|
|
979
|
+
return previous.control?.state==='paused'
|
|
980
|
+
?previous.control.resume()
|
|
981
|
+
:true;
|
|
982
|
+
}catch(error){
|
|
983
|
+
this.stopNativePlayback();
|
|
984
|
+
this.fail(error);
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
if(!this.stopNativePlayback())return false;
|
|
989
|
+
const playback=this;
|
|
990
|
+
const active={
|
|
991
|
+
generation:this.generation,
|
|
992
|
+
index:this.index,
|
|
993
|
+
controller:new AbortController(),
|
|
994
|
+
control:null,
|
|
995
|
+
completion:null,
|
|
996
|
+
releaseCompletion:null,
|
|
997
|
+
failed:false,
|
|
998
|
+
cancelled:false,
|
|
999
|
+
released:false
|
|
1000
|
+
};
|
|
1001
|
+
this.nativePlayback=active;
|
|
1002
|
+
this.abortControllers.add(active.controller);
|
|
1003
|
+
function currentNativePlayback(){
|
|
1004
|
+
return playback.nativePlayback===active
|
|
1005
|
+
&&playback.generation===active.generation
|
|
1006
|
+
&&playback.index===active.index
|
|
1007
|
+
&&!active.cancelled;
|
|
1008
|
+
}
|
|
1009
|
+
function releaseNativePlayback(){
|
|
1010
|
+
active.released=true;
|
|
1011
|
+
playback.abortControllers.delete(active.controller);
|
|
1012
|
+
if((active.failed||active.cancelled)&&playback.nativePlayback===active){
|
|
1013
|
+
playback.nativePlayback=null;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
function observeNativeState(detail){
|
|
1017
|
+
if(playback.nativePlayback===active
|
|
1018
|
+
&&(active.failed||active.cancelled)
|
|
1019
|
+
&&(detail.state==='complete'||detail.state==='stopped')){
|
|
1020
|
+
releaseNativePlayback();
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
if(!currentNativePlayback())return;
|
|
1024
|
+
if(active.failed)return;
|
|
1025
|
+
if(detail.state==='playing')playback.emit('playing',playback.message('playing'));
|
|
1026
|
+
else if(detail.state==='paused')playback.emit('paused',playback.message('paused'));
|
|
1027
|
+
else if(detail.state==='waiting-for-voices'){
|
|
1028
|
+
playback.emit('buffering',playback.message('waitingForVoices'));
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
async function observeNativeCompletion(){
|
|
1032
|
+
try{
|
|
1033
|
+
const completed=await active.control.finished;
|
|
1034
|
+
if(!currentNativePlayback())return false;
|
|
1035
|
+
playback.nativePlayback=null;
|
|
1036
|
+
playback.abortControllers.delete(active.controller);
|
|
1037
|
+
if(completed===true)return await playback.advance();
|
|
1038
|
+
if(active.control.error)playback.fail(active.control.error);
|
|
1039
|
+
else playback.emit('ready',playback.message('stopped'));
|
|
1040
|
+
return false;
|
|
1041
|
+
}catch(error){
|
|
1042
|
+
console.error('[Arcane speech playback] Native speech failed.',error);
|
|
1043
|
+
// A native control failure may leave the utterance running until stop or end.
|
|
1044
|
+
active.failed=true;
|
|
1045
|
+
if(playback.generation===active.generation
|
|
1046
|
+
&&(playback.nativePlayback===active||playback.nativePlayback===null)){
|
|
1047
|
+
playback.fail(error);
|
|
1048
|
+
}
|
|
1049
|
+
if(active.released)releaseNativePlayback();
|
|
1050
|
+
return false;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
async function observeNativeRelease(){
|
|
1054
|
+
try{
|
|
1055
|
+
await active.control.released;
|
|
1056
|
+
releaseNativePlayback();
|
|
1057
|
+
}catch(error){
|
|
1058
|
+
console.error('[Arcane speech playback] Native speech release failed.',error);
|
|
1059
|
+
if(currentNativePlayback())playback.fail(error);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
try{
|
|
1063
|
+
active.control=prepared.play({
|
|
1064
|
+
signal:active.controller.signal,
|
|
1065
|
+
onState:observeNativeState
|
|
1066
|
+
});
|
|
1067
|
+
active.completion=observeNativeCompletion();
|
|
1068
|
+
if(is.function(active.control.released?.then)){
|
|
1069
|
+
active.releaseCompletion=observeNativeRelease();
|
|
1070
|
+
}
|
|
1071
|
+
return currentNativePlayback()
|
|
1072
|
+
&&active.control.state!=='error'
|
|
1073
|
+
&&active.control.state!=='stopped';
|
|
1074
|
+
}catch(error){
|
|
1075
|
+
this.stopNativePlayback();
|
|
1076
|
+
this.fail(error);
|
|
1077
|
+
return false;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
893
1081
|
async restart(){
|
|
894
1082
|
if(!this.hasAudio())return false;
|
|
895
1083
|
this.generation+=1;
|
|
896
1084
|
const generation=this.generation;
|
|
1085
|
+
if(!this.stopNativePlayback())return false;
|
|
897
1086
|
if(this.providerSynthesisBatch){
|
|
898
1087
|
const providerSynthesisBatch=this.providerSynthesisBatch;
|
|
899
1088
|
for(const [index,segmentError] of this.segmentErrors){
|
|
900
1089
|
if(
|
|
901
1090
|
segmentError?.providerSynthesisBatch
|
|
902
1091
|
===providerSynthesisBatch
|
|
903
|
-
&&!this.
|
|
1092
|
+
&&!this.hasSegment(index)
|
|
904
1093
|
&&!this.segmentRequests.has(index)
|
|
905
1094
|
){
|
|
906
1095
|
startProviderSegment(
|
|
@@ -916,9 +1105,9 @@ class SpeechPlayback{
|
|
|
916
1105
|
}
|
|
917
1106
|
this.audio.pause();
|
|
918
1107
|
this.index=0;
|
|
919
|
-
this.loadCurrent();
|
|
1108
|
+
const loaded=this.loadCurrent();
|
|
920
1109
|
this.audio.currentTime=0;
|
|
921
|
-
const played=await this.play();
|
|
1110
|
+
const played=loaded?await this.play():false;
|
|
922
1111
|
if(generation===this.generation&&!this.providerSynthesisBatch){
|
|
923
1112
|
this.startLookahead(1,generation);
|
|
924
1113
|
}
|
|
@@ -929,6 +1118,18 @@ class SpeechPlayback{
|
|
|
929
1118
|
|
|
930
1119
|
async togglePause(){
|
|
931
1120
|
if(!this.hasAudio())return false;
|
|
1121
|
+
if(this.nativeSegments.has(this.index)){
|
|
1122
|
+
if(this.nativePlayback?.failed)return false;
|
|
1123
|
+
const control=this.nativePlayback?.control;
|
|
1124
|
+
if(!control||control.state==='paused')return this.play();
|
|
1125
|
+
try{
|
|
1126
|
+
return control.pause();
|
|
1127
|
+
}catch(error){
|
|
1128
|
+
this.stopNativePlayback();
|
|
1129
|
+
this.fail(error);
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
932
1133
|
if(this.audio.paused)return this.play();
|
|
933
1134
|
this.audio.pause();
|
|
934
1135
|
return true;
|
|
@@ -936,7 +1137,7 @@ class SpeechPlayback{
|
|
|
936
1137
|
|
|
937
1138
|
stop(){
|
|
938
1139
|
const hasAudio=this.hasAudio();
|
|
939
|
-
if(!this.synthesisInFlight&&!hasAudio)return;
|
|
1140
|
+
if(!this.synthesisInFlight&&!hasAudio&&!this.nativePlayback)return;
|
|
940
1141
|
this.cancel(
|
|
941
1142
|
this.message(hasAudio?'stopped':'preparationStopped'),
|
|
942
1143
|
'playback-stopped'
|
|
@@ -958,7 +1159,7 @@ class SpeechPlayback{
|
|
|
958
1159
|
}
|
|
959
1160
|
}
|
|
960
1161
|
const nextIndex=this.index+1;
|
|
961
|
-
if(!this.
|
|
1162
|
+
if(!this.hasSegment(nextIndex)){
|
|
962
1163
|
this.emit('buffering',this.message('buffering'));
|
|
963
1164
|
const ready=await this.waitForSegment(nextIndex,generation);
|
|
964
1165
|
if(!ready||generation!==this.generation){
|
|
@@ -978,8 +1179,8 @@ class SpeechPlayback{
|
|
|
978
1179
|
}
|
|
979
1180
|
}
|
|
980
1181
|
this.index=nextIndex;
|
|
981
|
-
this.loadCurrent();
|
|
982
|
-
const played=await this.play();
|
|
1182
|
+
const loaded=this.loadCurrent();
|
|
1183
|
+
const played=loaded?await this.play():false;
|
|
983
1184
|
if(generation===this.generation&&!this.providerSynthesisBatch){
|
|
984
1185
|
this.startLookahead(nextIndex+1,generation);
|
|
985
1186
|
}
|
|
@@ -987,6 +1188,7 @@ class SpeechPlayback{
|
|
|
987
1188
|
}
|
|
988
1189
|
|
|
989
1190
|
async handleEnded(){
|
|
1191
|
+
if(this.nativeSegments.has(this.index))return;
|
|
990
1192
|
try{
|
|
991
1193
|
await this.advance();
|
|
992
1194
|
}catch(error){
|
|
@@ -994,15 +1196,19 @@ class SpeechPlayback{
|
|
|
994
1196
|
}
|
|
995
1197
|
}
|
|
996
1198
|
|
|
997
|
-
handlePlay(){
|
|
1199
|
+
handlePlay(){
|
|
1200
|
+
if(!this.nativeSegments.has(this.index))this.emit('playing',this.message('playing'));
|
|
1201
|
+
}
|
|
998
1202
|
|
|
999
1203
|
handlePause(){
|
|
1204
|
+
if(this.nativeSegments.has(this.index))return;
|
|
1000
1205
|
if(this.hasAudio()&&this.audio.currentTime>0&&!this.audio.ended){
|
|
1001
1206
|
this.emit('paused',this.message('paused'));
|
|
1002
1207
|
}
|
|
1003
1208
|
}
|
|
1004
1209
|
|
|
1005
1210
|
handleError(){
|
|
1211
|
+
if(this.nativeSegments.has(this.index))return;
|
|
1006
1212
|
const error=new Error(this.message('playbackError'));
|
|
1007
1213
|
error.code='ARCANE_SPEECH_PLAYBACK_AUDIO_PLAYBACK_REJECTED';
|
|
1008
1214
|
this.fail(error);
|