提交
This commit is contained in:
parent
63a0438406
commit
71db19a4be
|
|
@ -126,6 +126,31 @@ public class AudioFilesConfig
|
|||
/// </summary>
|
||||
public int MinKeyTonePlayTimeMs { get; set; } = 200;
|
||||
|
||||
/// <summary>
|
||||
/// 按键音音量倍数 (正常音量为1.0)
|
||||
/// </summary>
|
||||
public float KeyToneVolume { get; set; } = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// 待机嘟声音量倍数 (正常音量为1.0)
|
||||
/// </summary>
|
||||
public float WaitingToneVolume { get; set; } = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// 拨出嘟声音量倍数 (正常音量为1.0)
|
||||
/// </summary>
|
||||
public float DialToneVolume { get; set; } = 0.9f;
|
||||
|
||||
/// <summary>
|
||||
/// 提示留言音量倍数 (正常音量为1.0)
|
||||
/// </summary>
|
||||
public float PromptAfterBeepVolume { get; set; } = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// 风铃提示音文件名
|
||||
/// </summary>
|
||||
public string WindChimeFile { get; set; } = "风铃.wav";
|
||||
|
||||
/// <summary>
|
||||
/// 获取完整的音频文件路径
|
||||
/// </summary>
|
||||
|
|
@ -234,6 +259,16 @@ public class RecordingConfig
|
|||
/// 背景音乐音量 (0.0-1.0)
|
||||
/// </summary>
|
||||
public float BackgroundMusicVolume { get; set; } = 0.1f;
|
||||
|
||||
/// <summary>
|
||||
/// 无声音播放风铃提示的时间(秒)
|
||||
/// </summary>
|
||||
public float WindChimePromptSeconds { get; set; } = 7.5f;
|
||||
|
||||
/// <summary>
|
||||
/// 风铃声淡出的时间(毫秒)
|
||||
/// </summary>
|
||||
public int WindChimeFadeOutMs { get; set; } = 2000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -25,9 +25,15 @@
|
|||
"WaitingToneFile": "等待嘟音.wav", // 等待嘟音文件
|
||||
"WaitForPickupFile": "等待接电话.mp3", // 等待接电话音频文件
|
||||
"PhonePickupFile": "电话接起.mp3", // 电话接起音频文件
|
||||
"PromptUserRecordFile": "提示用户录音.mp3", // 提示用户录音音频文件
|
||||
"PromptUserRecordFile": "提示用户录音.mp3", // 提示用户录音音频文件
|
||||
"BeepPromptFile": "滴提示音.wav", // 滴提示音文件
|
||||
"DigitToneFileTemplate": "{0}.mp3" // 数字按键音频文件模板
|
||||
"DigitToneFileTemplate": "{0}.mp3", // 数字按键音频文件模板
|
||||
"MinKeyTonePlayTimeMs": 200, // 按键音的最小播放时间(毫秒)
|
||||
"KeyToneVolume": 1.0, // 按键音量倍数 (0.0-1.0)
|
||||
"WaitingToneVolume": 1.0, // 待机嘟声音量倍数 (0.0-1.0)
|
||||
"DialToneVolume": 0.9, // 拨出嘟声音量倍数 (0.0-1.0)
|
||||
"PromptAfterBeepVolume": 1.0, // 提示留言音量倍数 (0.0-1.0)
|
||||
"WindChimeFile": "风铃.wav" // 风铃提示音文件名
|
||||
},
|
||||
"Dial": {
|
||||
"MinDigitsToDialOut": 8, // 拨号所需的最小位数
|
||||
|
|
@ -39,15 +45,22 @@
|
|||
"RecordingDeviceNumber": 0, // 录音设备编号
|
||||
"SampleRate": 16000, // 录音采样率
|
||||
"Channels": 1, // 录音通道数(1=单声道,2=立体声)
|
||||
"BufferMilliseconds": 100, // 录音缓冲区大小(毫秒)
|
||||
"SilenceThreshold": 0.02, // 静音检测阈值
|
||||
"BufferMilliseconds": 50, // 录音缓冲区大小(毫秒)
|
||||
"SilenceThreshold": 0.05, // 静音检测阈值
|
||||
"SilenceTimeoutSeconds": 30, // 无声音自动挂断的时间(秒)
|
||||
"AllowUserHangup": true // 是否允许用户手动挂断(按回车键)
|
||||
"AllowUserHangup": true, // 是否允许用户手动挂断(按回车键)
|
||||
"UploadRecordingToServer": false, // 是否上传录音文件到服务器
|
||||
"EnableBackgroundMusic": true, // 是否在录音时播放背景音乐
|
||||
"BackgroundMusicFile": "bj.mp3", // 录音背景音乐文件名
|
||||
"BackgroundMusicVolume": 0.1, // 背景音乐音量 (0.0-1.0)
|
||||
"WindChimePromptSeconds": 7.5, // 无声音播放风铃提示的时间(秒)
|
||||
"WindChimeFadeOutMs": 3000 // 风铃声淡出的时间(毫秒)
|
||||
},
|
||||
"CallFlow": {
|
||||
"WaitForPickupMinSeconds": 3, // 等待接电话音频的最小持续时间(秒)
|
||||
"WaitForPickupMaxSeconds": 6, // 等待接电话音频的最大持续时间(秒)
|
||||
"PlayPickupProbability": 0.15 // 播放电话接起音频的概率(0-1之间的小数)
|
||||
"PlayPickupProbability": 0.15, // 播放电话接起音频的概率(0-1之间的小数)
|
||||
"ResetSystemAfterHangup": false // 用户挂断后是否重置系统,false表示直接退出程序
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -70,6 +70,14 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
|
|||
private CancellationTokenSource? _backgroundMusicCts = null;
|
||||
private volatile bool _isBackgroundMusicPlaying = false;
|
||||
|
||||
// 风铃提示音相关
|
||||
private WaveOutEvent? _windChimeDevice = null;
|
||||
private AudioFileReader? _windChimeReader = null;
|
||||
private CancellationTokenSource? _windChimeCts = null;
|
||||
private volatile bool _isWindChimePlaying = false;
|
||||
private Timer? _windChimeTimer = null;
|
||||
private DateTime _lastSpeechTime = DateTime.MinValue;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct KBDLLHOOKSTRUCT
|
||||
{
|
||||
|
|
@ -282,13 +290,12 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
|
|||
_waitingToneDevice = new WaveOutEvent();
|
||||
_waitingToneReader = new AudioFileReader(_config.AudioFiles.GetFullPath(_config.AudioFiles.WaitingToneFile));
|
||||
_waitingToneDevice.Init(_waitingToneReader);
|
||||
|
||||
Console.WriteLine("音频设备初始化成功");
|
||||
// 设置待机嘟声音量,确保在0.0-1.0范围内
|
||||
_waitingToneDevice.Volume = Math.Clamp(_config.AudioFiles.WaitingToneVolume > 0 ? _config.AudioFiles.WaitingToneVolume : 1.0f, 0.0f, 1.0f);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"初始化音频设备失败: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -334,6 +341,28 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
|
|||
|
||||
_isBackgroundMusicPlaying = false;
|
||||
|
||||
// 释放风铃提示音设备
|
||||
_windChimeCts?.Cancel();
|
||||
_windChimeCts?.Dispose();
|
||||
_windChimeCts = null;
|
||||
|
||||
if (_windChimeDevice != null)
|
||||
{
|
||||
_windChimeDevice.Stop();
|
||||
_windChimeDevice.Dispose();
|
||||
_windChimeDevice = null;
|
||||
}
|
||||
|
||||
if (_windChimeReader != null)
|
||||
{
|
||||
_windChimeReader.Dispose();
|
||||
_windChimeReader = null;
|
||||
}
|
||||
|
||||
_isWindChimePlaying = false;
|
||||
_windChimeTimer?.Dispose();
|
||||
_windChimeTimer = null;
|
||||
|
||||
foreach (var (device, reader) in _keyToneDevices.Values)
|
||||
{
|
||||
device.Stop();
|
||||
|
|
@ -552,14 +581,18 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
|
|||
|
||||
// 启动静音检测计时器
|
||||
_lastSoundTime = DateTime.Now;
|
||||
_lastSpeechTime = DateTime.Now; // 初始化最后说话时间
|
||||
_silenceTimer = new Timer(CheckSilence, recordingCompletionSource, 1000, 1000);
|
||||
|
||||
// 启动风铃提示计时器
|
||||
_windChimeTimer = new Timer(CheckWindChimePrompt, null, 1000, 1000);
|
||||
|
||||
Console.WriteLine("播放滴提示音...");
|
||||
try
|
||||
{
|
||||
await PlayAudioAndWait(
|
||||
_config.AudioFiles.GetFullPath(_config.AudioFiles.BeepPromptFile),
|
||||
null, false, recordingCts.Token, 0.1f);
|
||||
null, false, recordingCts.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
|
@ -838,6 +871,14 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
|
|||
_silenceTimer?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
_silenceTimer?.Dispose();
|
||||
_silenceTimer = null;
|
||||
|
||||
// 停止风铃提示音计时器
|
||||
_windChimeTimer?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
_windChimeTimer?.Dispose();
|
||||
_windChimeTimer = null;
|
||||
|
||||
// 停止风铃提示音
|
||||
_ = StopWindChime(false);
|
||||
|
||||
// 停止录音
|
||||
if (_waveIn != null)
|
||||
|
|
@ -909,6 +950,19 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
|
|||
_lasHasoudDateTime = DateTime.Now;
|
||||
_isSpeaking = false;
|
||||
}
|
||||
|
||||
// 如果监测到说话,更新最后说话时间
|
||||
if (isSpeaking)
|
||||
{
|
||||
_lastSpeechTime = DateTime.Now;
|
||||
|
||||
// 如果风铃声正在播放,平滑停止它
|
||||
if (_isWindChimePlaying)
|
||||
{
|
||||
_ = StopWindChime(true); // 平滑淡出
|
||||
}
|
||||
}
|
||||
|
||||
// 如果最大音量超过阈值,则认为有声音
|
||||
return isSpeaking;
|
||||
}
|
||||
|
|
@ -925,6 +979,9 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
|
|||
|
||||
// 确保背景音乐被停止
|
||||
StopBackgroundMusic();
|
||||
|
||||
// 确保风铃提示音被停止
|
||||
_ = StopWindChime(false);
|
||||
|
||||
// 清除录音状态
|
||||
_isRecording = false;
|
||||
|
|
@ -1238,6 +1295,8 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
|
|||
var device = new WaveOutEvent();
|
||||
var reader = new AudioFileReader(_config.AudioFiles.GetDigitToneFilePath(digit));
|
||||
device.Init(reader);
|
||||
// 设置按键音量,确保在0.0-1.0范围内
|
||||
device.Volume = Math.Clamp(_config.AudioFiles.KeyToneVolume > 0 ? _config.AudioFiles.KeyToneVolume : 1.0f, 0.0f, 1.0f);
|
||||
|
||||
// 记录开始播放时间
|
||||
var startTime = DateTime.Now;
|
||||
|
|
@ -1245,6 +1304,7 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
|
|||
|
||||
if (_keyToneDevices.TryAdd(digit, (device, reader)))
|
||||
{
|
||||
|
||||
device.Play();
|
||||
|
||||
// 确保至少播放最小时长
|
||||
|
|
@ -1301,17 +1361,44 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
|
|||
{
|
||||
device = new WaveOutEvent();
|
||||
reader = new AudioFileReader(audioPath);
|
||||
//device.Volume
|
||||
if (volume != null)
|
||||
|
||||
// 如果没有指定音量,根据音频文件类型设置音量
|
||||
if (volume == null)
|
||||
{
|
||||
device.Volume = volume.Value;
|
||||
// 根据文件名判断音频类型并设置对应的音量
|
||||
if (audioPath.Contains(_config.AudioFiles.WaitForPickupFile))
|
||||
{
|
||||
// 拨出嘟声音量
|
||||
device.Volume = Math.Clamp(_config.AudioFiles.DialToneVolume > 0 ? _config.AudioFiles.DialToneVolume : 1.0f, 0.0f, 1.0f);
|
||||
}
|
||||
else if (audioPath.Contains(_config.AudioFiles.PromptUserRecordFile))
|
||||
{
|
||||
// 提示用户录音的音量
|
||||
device.Volume = Math.Clamp(_config.AudioFiles.PromptAfterBeepVolume > 0 ? _config.AudioFiles.PromptAfterBeepVolume : 1.0f, 0.0f, 1.0f);
|
||||
}
|
||||
else if (audioPath.Contains(_config.AudioFiles.BeepPromptFile))
|
||||
{
|
||||
// 滴提示音的音量
|
||||
device.Volume = Math.Clamp(_config.AudioFiles.PromptAfterBeepVolume > 0 ? _config.AudioFiles.PromptAfterBeepVolume : 1.0f, 0.0f, 1.0f);
|
||||
}
|
||||
else if (audioPath.Contains(_config.AudioFiles.WaitingToneFile))
|
||||
{
|
||||
// 待机嘟声音量 (虽然这个可能不会通过这个方法播放)
|
||||
device.Volume = Math.Clamp(_config.AudioFiles.WaitingToneVolume > 0 ? _config.AudioFiles.WaitingToneVolume : 1.0f, 0.0f, 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 默认音量为1.0
|
||||
device.Volume = 1.0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
device.Volume = Math.Clamp(volume.Value, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
device.Init(reader);
|
||||
//device.Volume
|
||||
if (volume != null)
|
||||
{
|
||||
device.Volume = volume.Value;
|
||||
}
|
||||
|
||||
// 如果需要循环播放,创建一个循环播放的任务
|
||||
if (loop && maxDuration.HasValue)
|
||||
{
|
||||
|
|
@ -1518,4 +1605,182 @@ public class PhoneBoothService : IPhoneBoothService, IDisposable
|
|||
Console.WriteLine($"停止背景音乐失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否需要播放风铃提示音
|
||||
/// </summary>
|
||||
private void CheckWindChimePrompt(object? state)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果用户已挂断或者录音已停止,不执行操作
|
||||
if (_isHangUpKeyPressed || !_isRecording)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否超过配置的无声音时间但还未达到挂断时间
|
||||
float windChimePromptSeconds = _config.Recording.WindChimePromptSeconds;
|
||||
int silenceTimeoutSeconds = _config.Recording.SilenceTimeoutSeconds;
|
||||
double secondsSinceLastSpeech = (DateTime.Now - _lastSpeechTime).TotalSeconds;
|
||||
|
||||
// 如果超过风铃提示时间但未达到挂断时间,并且风铃没在播放,开始播放风铃
|
||||
if (secondsSinceLastSpeech >= windChimePromptSeconds &&
|
||||
secondsSinceLastSpeech < silenceTimeoutSeconds &&
|
||||
!_isWindChimePlaying)
|
||||
{
|
||||
Console.WriteLine($"检测到{windChimePromptSeconds}秒无声音,播放风铃提示音...");
|
||||
_ = PlayWindChime();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"检查风铃提示音错误: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 播放风铃提示音
|
||||
/// </summary>
|
||||
private async Task PlayWindChime()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果已经在播放,先停止
|
||||
if (_isWindChimePlaying)
|
||||
{
|
||||
await StopWindChime(true); // 平滑淡出
|
||||
}
|
||||
|
||||
// 防止重复启动
|
||||
if (_isWindChimePlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_windChimeCts?.Cancel();
|
||||
_windChimeCts?.Dispose();
|
||||
_windChimeCts = CancellationTokenSource.CreateLinkedTokenSource(_programCts.Token);
|
||||
var token = _windChimeCts.Token;
|
||||
|
||||
string windChimeFilePath = _config.AudioFiles.GetFullPath(_config.AudioFiles.WindChimeFile);
|
||||
if (!File.Exists(windChimeFilePath))
|
||||
{
|
||||
Console.WriteLine($"风铃提示音文件不存在: {windChimeFilePath}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化播放设备
|
||||
_windChimeDevice = new WaveOutEvent();
|
||||
_windChimeReader = new AudioFileReader(windChimeFilePath);
|
||||
_windChimeDevice.Init(_windChimeReader);
|
||||
_windChimeDevice.Volume = Math.Clamp(1.0f, 0.0f, 1.0f); // 初始音量设为最大
|
||||
|
||||
_isWindChimePlaying = true;
|
||||
Console.WriteLine("开始播放风铃提示音");
|
||||
|
||||
// 开始播放
|
||||
_windChimeDevice.Play();
|
||||
|
||||
// 单独开启一个任务检测是否有声音,如有则淡出停止
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
while (_isWindChimePlaying && !token.IsCancellationRequested)
|
||||
{
|
||||
// 如果检测到有声音,淡出停止
|
||||
if ((DateTime.Now - _lastSpeechTime).TotalSeconds < 1.0)
|
||||
{
|
||||
Console.WriteLine("检测到用户说话,风铃提示音淡出...");
|
||||
await StopWindChime(true); // 平滑淡出
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果到达音频文件末尾,循环播放
|
||||
if (_windChimeReader != null && _windChimeReader.Position >= _windChimeReader.Length)
|
||||
{
|
||||
_windChimeReader.Position = 0;
|
||||
}
|
||||
|
||||
await Task.Delay(100, token);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 正常取消
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"风铃提示音监控错误: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"播放风铃提示音错误: {ex.Message}");
|
||||
await StopWindChime(false); // 出错时直接停止,不淡出
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止风铃提示音
|
||||
/// </summary>
|
||||
/// <param name="fadeOut">是否平滑淡出</param>
|
||||
private async Task StopWindChime(bool fadeOut)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_isWindChimePlaying)
|
||||
{
|
||||
return; // 如果没有播放,不需要停止
|
||||
}
|
||||
|
||||
// 如果需要淡出
|
||||
if (fadeOut && _windChimeDevice != null)
|
||||
{
|
||||
int fadeOutMs = _config.Recording.WindChimeFadeOutMs;
|
||||
float startVolume = _windChimeDevice.Volume;
|
||||
int steps = 20; // 淡出的步骤数
|
||||
int stepDelay = fadeOutMs / steps;
|
||||
|
||||
// 逐步降低音量实现淡出效果
|
||||
for (int i = 0; i < steps; i++)
|
||||
{
|
||||
if (_windChimeDevice == null || !_isWindChimePlaying)
|
||||
{
|
||||
break; // 如果设备已释放或播放已停止,直接退出
|
||||
}
|
||||
|
||||
float ratio = 1.0f - ((float)i / steps);
|
||||
_windChimeDevice.Volume = startVolume * ratio;
|
||||
await Task.Delay(stepDelay);
|
||||
}
|
||||
}
|
||||
|
||||
// 正式停止
|
||||
_windChimeCts?.Cancel();
|
||||
|
||||
if (_windChimeDevice != null)
|
||||
{
|
||||
_windChimeDevice.Stop();
|
||||
_windChimeDevice.Dispose();
|
||||
_windChimeDevice = null;
|
||||
}
|
||||
|
||||
if (_windChimeReader != null)
|
||||
{
|
||||
_windChimeReader.Dispose();
|
||||
_windChimeReader = null;
|
||||
}
|
||||
|
||||
_isWindChimePlaying = false;
|
||||
Console.WriteLine("风铃提示音已停止");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"停止风铃提示音错误: {ex.Message}");
|
||||
_isWindChimePlaying = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"SignalRHubUrl": "http://localhost:81/audiohub",
|
||||
"SignalRHubUrl": "http://115.159.44.16/audiohub",
|
||||
"ConfigBackupPath": "config.json",
|
||||
"AutoConnectToServer": true,
|
||||
"AllowOfflineStart": false
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@
|
|||
"PromptUserRecordFile": "\u63D0\u793A\u7528\u6237\u5F55\u97F3.mp3",
|
||||
"BeepPromptFile": "\u6EF4\u63D0\u793A\u97F3.wav",
|
||||
"DigitToneFileTemplate": "{0}.mp3",
|
||||
"MinKeyTonePlayTimeMs": 200
|
||||
"MinKeyTonePlayTimeMs": 200,
|
||||
"KeyToneVolume": 1.0,
|
||||
"WaitingToneVolume": 1.0,
|
||||
"DialToneVolume": 0.9,
|
||||
"PromptAfterBeepVolume": 1.0,
|
||||
"WindChimeFile": "\u98CE\u94C3.wav"
|
||||
},
|
||||
"Dial": {
|
||||
"MinDigitsToDialOut": 8,
|
||||
|
|
@ -20,13 +25,15 @@
|
|||
"SampleRate": 16000,
|
||||
"Channels": 1,
|
||||
"BufferMilliseconds": 50,
|
||||
"SilenceThreshold": 0.15,
|
||||
"SilenceTimeoutSeconds": 30,
|
||||
"SilenceThreshold": 0.1,
|
||||
"SilenceTimeoutSeconds": 15,
|
||||
"AllowUserHangup": true,
|
||||
"UploadRecordingToServer": false,
|
||||
"EnableBackgroundMusic": true,
|
||||
"BackgroundMusicFile": "bj.mp3",
|
||||
"BackgroundMusicVolume": 0.1
|
||||
"BackgroundMusicVolume": 0.3,
|
||||
"WindChimePromptSeconds": 7.5,
|
||||
"WindChimeFadeOutMs": 3000
|
||||
},
|
||||
"CallFlow": {
|
||||
"WaitForPickupMinSeconds": 3,
|
||||
|
|
|
|||
|
|
@ -137,10 +137,13 @@
|
|||
},
|
||||
// 右侧容器配置
|
||||
rightContainer: {
|
||||
fontSize: '40px', // 右侧文字大小
|
||||
fontWeight: '700', // 右侧文字粗细
|
||||
fontStyle: 'italic', // 右侧文字样式
|
||||
typewriterSpeed: 2000 // 右侧文字打字机速度(毫秒),增加到2000毫秒
|
||||
fontSize: '40px', // 右侧文字大小
|
||||
fontWeight: '700', // 右侧文字粗细
|
||||
fontStyle: 'italic', // 右侧文字样式
|
||||
typewriterSpeed: 2000, // 右侧文字打字机速度(毫秒),增加到2000毫秒
|
||||
fadeChars: 4, // 渐变字符数量,值越大渐变效果越长
|
||||
fadeStepTime: 500, // 字符透明度过渡时间(毫秒)
|
||||
fadeDelayFactor: 0.25 // 每个字符的透明度递减因子(0-1)
|
||||
},
|
||||
// 水波纹效果配置
|
||||
waterEffect: {
|
||||
|
|
@ -199,34 +202,92 @@
|
|||
if (isAnimating) return;
|
||||
isAnimating = true;
|
||||
|
||||
var i = 0;
|
||||
$(selector).html('').css('opacity', 1);
|
||||
// 获取渐变相关配置
|
||||
const fadeChars = CONFIG.rightContainer.fadeChars || 4; // 渐变字符数量
|
||||
const fadeStepTime = CONFIG.rightContainer.fadeStepTime || 500; // 过渡时间
|
||||
const fadeDelayFactor = CONFIG.rightContainer.fadeDelayFactor || 0.25; // 透明度递减因子
|
||||
|
||||
function typeWriter() {
|
||||
if (i < text.length) {
|
||||
// 创建一个新的 span 元素包含当前字符
|
||||
const charSpan = $('<span></span>').text(text.charAt(i));
|
||||
charSpan.css('opacity', 0); // 初始透明度为0
|
||||
$(selector).append(charSpan); // 添加到容器
|
||||
|
||||
// 对这个字符应用淡入效果
|
||||
charSpan.animate({opacity: 1}, speed * 0.8, function() {
|
||||
// 淡入完成后继续下一个字符
|
||||
i++;
|
||||
setTimeout(typeWriter, speed * 0.2);
|
||||
});
|
||||
} else {
|
||||
isAnimating = false;
|
||||
if (callback) callback();
|
||||
|
||||
// 打字效果完成后,等待随机4-7秒,然后请求下一条文本
|
||||
const delay = Math.floor(Math.random() * 3000) + 4000; // 4000-7000毫秒
|
||||
console.log(`文本显示完成,将在${delay/1000}秒后请求下一条文本`);
|
||||
|
||||
setTimeout(requestNextText, delay);
|
||||
}
|
||||
// 清空容器并设置初始可见度
|
||||
$(selector).html('').css('opacity', 1);
|
||||
|
||||
// 创建所有字符元素,但初始透明度为0
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const charSpan = $('<span></span>').text(text.charAt(i));
|
||||
charSpan.css({
|
||||
'opacity': 0,
|
||||
'display': 'inline-block', // 确保每个字符是独立的块
|
||||
'transition': `opacity ${fadeStepTime}ms ease-in-out` // 平滑的透明度过渡效果
|
||||
});
|
||||
$(selector).append(charSpan);
|
||||
}
|
||||
typeWriter();
|
||||
|
||||
// 获取所有字符span元素
|
||||
const chars = $(selector).find('span');
|
||||
const totalChars = chars.length;
|
||||
|
||||
// 设置初始不透明度,形成从左到右的渐变效果
|
||||
// 左侧字符较深,右侧字符较浅
|
||||
const setInitialOpacity = () => {
|
||||
for (let i = 0; i < totalChars; i++) {
|
||||
// 计算不透明度:第一个字符不透明度最高,往后逐渐降低
|
||||
let position = i / totalChars; // 字符在文本中的相对位置 (0-1)
|
||||
let opacity = Math.max(0, 0.8 - (position * fadeChars * 0.2));
|
||||
|
||||
// 超出可见范围的字符完全透明
|
||||
if (i >= fadeChars) {
|
||||
opacity = 0;
|
||||
}
|
||||
|
||||
// 设置不透明度
|
||||
$(chars[i]).css('opacity', opacity);
|
||||
}
|
||||
};
|
||||
|
||||
// 执行渐变动画
|
||||
const animateText = () => {
|
||||
let progress = 0;
|
||||
|
||||
const step = () => {
|
||||
if (progress >= 1) {
|
||||
// 完成动画
|
||||
chars.css('opacity', 1);
|
||||
isAnimating = false;
|
||||
if (callback) callback();
|
||||
|
||||
// 等待随机4-7秒后请求下一条文本
|
||||
const delay = Math.floor(Math.random() * 3000) + 4000; // 4000-7000毫秒
|
||||
console.log(`文本显示完成,将在${delay/1000}秒后请求下一条文本`);
|
||||
setTimeout(requestNextText, delay);
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新每个字符的透明度
|
||||
for (let i = 0; i < totalChars; i++) {
|
||||
let targetOpacity = 1; // 目标不透明度(完全显示)
|
||||
let currentPosition = i / totalChars; // 字符的相对位置
|
||||
|
||||
// 计算当前动画进度下该位置的字符应有的透明度
|
||||
// 小于当前进度的字符应该更不透明
|
||||
let opacity = targetOpacity * Math.max(0, 1 - Math.max(0, (currentPosition - progress) * 3));
|
||||
|
||||
// 设置不透明度
|
||||
$(chars[i]).css('opacity', opacity);
|
||||
}
|
||||
|
||||
// 增加进度
|
||||
progress += 0.02;
|
||||
|
||||
// 继续下一步动画
|
||||
setTimeout(step, speed / 50);
|
||||
};
|
||||
|
||||
// 开始动画
|
||||
step();
|
||||
};
|
||||
|
||||
// 设置初始不透明度并开始动画
|
||||
setInitialOpacity();
|
||||
setTimeout(animateText, 50);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -422,6 +483,17 @@
|
|||
CONFIG.rightContainer.fontStyle = newConfig.rightContainer.fontStyle || CONFIG.rightContainer.fontStyle;
|
||||
CONFIG.rightContainer.typewriterSpeed = newConfig.rightContainer.typewriterSpeed || CONFIG.rightContainer.typewriterSpeed;
|
||||
|
||||
// 更新渐显效果相关的配置
|
||||
if (newConfig.rightContainer.fadeChars !== undefined) {
|
||||
CONFIG.rightContainer.fadeChars = newConfig.rightContainer.fadeChars;
|
||||
}
|
||||
if (newConfig.rightContainer.fadeStepTime !== undefined) {
|
||||
CONFIG.rightContainer.fadeStepTime = newConfig.rightContainer.fadeStepTime;
|
||||
}
|
||||
if (newConfig.rightContainer.fadeDelayFactor !== undefined) {
|
||||
CONFIG.rightContainer.fadeDelayFactor = newConfig.rightContainer.fadeDelayFactor;
|
||||
}
|
||||
|
||||
// 应用到CSS
|
||||
document.documentElement.style.setProperty('--right-font-size', CONFIG.rightContainer.fontSize);
|
||||
document.documentElement.style.setProperty('--right-font-weight', CONFIG.rightContainer.fontWeight);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@
|
|||
"fontSize": "40px",
|
||||
"fontWeight": "bolder",
|
||||
"fontStyle": "italic",
|
||||
"typewriterSpeed": 500
|
||||
"typewriterSpeed": 6000,
|
||||
"fadeChars": 1,
|
||||
"fadeStepTime": 1000,
|
||||
"fadeDelayFactor": 0.20
|
||||
},
|
||||
"waterEffect": {
|
||||
"enabled": true,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user