Я нашел работоспособное, хотя и не идеальное решение, как приостановить и возобновить воспроизведение с Twilio.
Основная идея заключается в вычислении разницы во времени между созданием команды воспроизведения и временем, когда вызывается URL-адрес Gather
. Разница (предполагая идеальный мир на мгновение) должна заключаться в том, насколько далеко игра была доведена до прерывания вызова. Затем, когда вызывающий абонент готов к возобновлению, сгенерируйте команду Play
, чтобы сервер вашего приложения доставлял не полный контент, а скорее частичный смещенный контент, который начинается прямо в точке, где воспроизведение должно возобновляться (что, вероятно, означает, что механизм доставить только часть содержимого аудиофайла необходимо будет реализовать). Это будет по существу эмулировать функциональность паузы/возобновления.
Я реализовал это и это более или менее работает. Неисправный мир вступает в игру, когда латентность сети, задержки в обработке (время между Twilio принимает команду Play
, извлекает игровой ресурс и фактически начинает воспроизведение), а также задержка между нажатием кнопки и фактическим получением вызова Gather
все влияет на точность. Но если ваши требования не слишком строги, точность, вероятно, достаточно приемлема для большинства случаев.
Вот доказательство концепции, которое я сделал в C# (прошло несколько месяцев - надеюсь, что все еще работает как опубликовано). Он также включает в себя эксперименты с быстрой перемоткой и перемоткой, которые просто корректируются, когда начинается фактическое начало (и пропускается команда Pause
).
Код ниже для PausablePlayController.cs, который генерирует TwiML с Play
, Pause
и других команд.
Play
Действие (не команда TwiML) генерирует TwiML для воспроизведения контента. Воспроизведение прерывается, поскольку оно завернуто в Gather
, что указывает на действие Pause
. URL-адрес Gather
содержит отметку времени начала воспроизведения (и в случае, если он уже был смещен ранее, вычисляет его обратно во времени).
Pause
Действие (не команда TwiML) генерирует TwiML для выполнения паузы или поиска. В коде ниже 4 перематывается, 5 перезапускается с начала, 6 быстрых переходов, и любой другой ключ делает паузу.
public class PausablePlayController : ApiController
{
private const int seekDeltaMilliseconds = 5000;
// GET api/pausableplay/5
[HttpGet]
public System.Xml.Linq.XElement Play(string audio, int millisecondsOffset)
{
TwilioResponse twiml = new TwilioResponse();
twiml.BeginGather(new { action = this.Url.Link("PausablePlayPause", new { audio = audio, playStart = DateTime.UtcNow.Subtract(new TimeSpan(0, 0, 0, 0, millisecondsOffset)).Ticks/*.ToString("o", System.Globalization.CultureInfo.InvariantCulture)*/ }), method = "GET", numDigits = "1" });
twiml.Play(this.Url.Link("OffsetPresentations", new { audio = audio, millisecondsOffset = millisecondsOffset }));
twiml.EndGather();
return twiml.Element;
}
[HttpGet]
public System.Xml.Linq.XElement Pause(string audio, long playStart, int digits)
{
DateTime playStartDate = new DateTime(playStart, DateTimeKind.Utc);
int millisecondsOffset = (int)DateTime.UtcNow.Subtract(playStartDate).TotalMilliseconds;
TwilioResponse twiml = new TwilioResponse();
switch(digits)
{
case 4:
millisecondsOffset -= (millisecondsOffset < seekDeltaMilliseconds) ? millisecondsOffset : seekDeltaMilliseconds;
return Play(audio, millisecondsOffset);
case 5:
return Play(audio, 0);
case 6:
millisecondsOffset += seekDeltaMilliseconds;
return Play(audio, millisecondsOffset);
default:
{
twiml.BeginGather(new { action = this.Url.Link("PausablePlayPlay", new { audio = audio, millisecondsOffset = millisecondsOffset }), method = "GET", numDigits = "1" });
twiml.Pause(120);
twiml.EndGather();
twiml.Say("Goodbye!");
}
break;
}
return twiml.Element;
}
}
Остальная часть трик в следующем контроллере, что потоки частичного звукового содержимого (части коды я нашел в каком-либо другом посте, на который я больше не имею ссылки, к сожалению).Все, что он делает, просто вычисляет, где начинается аудиосодержание для заданных миллисекунд смещения и передает остальную часть содержимого с этой точки.
public class OffsetedContentController : ApplicationController
{
const int BufferSize = 32 * 1024;
// GET api/prompts/5
public Task<HttpResponseMessage> Get(string audio, [FromUri]int millisecondsOffset)
{
string contentFilePath = audio; // Build physical path for your audio content
if (!File.Exists(contentFilePath))
{
return Task.FromResult(Request.CreateResponse(HttpStatusCode.NotFound));
}
// Open file and read response from it. If read fails then return 503 Service Not Available
try
{
// Create StreamContent from FileStream. FileStream will get closed when StreamContent is closed
FileStream fStream = new FileStream(contentFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize, useAsync: true);
fStream.Position = getPositionOffset(millisecondsOffset);
HttpResponseMessage response = Request.CreateResponse();
response.Content = new StreamContent(fStream);
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("audio/ulaw");
return Task.FromResult(response);
}
catch (Exception e)
{
return Task.FromResult(Request.CreateErrorResponse(HttpStatusCode.ServiceUnavailable, e));
}
}
private long getPositionOffset(int millisecondsOffset)
{
long bytePosition = millisecondsOffset * 4;
return bytePosition;
}
}
Это примерно тот подход, который я принимаю - задача состоит в том, чтобы выяснить, в какой момент во время воспроизведения произошло прерывание. Я думаю, что у меня есть решение, следите за обновлениями ... – LB2
@ LB2, вы нашли решение для этого? Можно ли узнать, где было воспроизведено воспроизведение файла при вводе данных? – jdmcnair
@ LB2 Я нашел способ подделать его (и опубликует подход, возможно, на следующей неделе), хотя он не идеален и подвержен отклонениям в сети и другим «перекрестным ветрам». У меня нет доступа к моему доказательству концепции, чтобы опубликовать мой ответ прямо сейчас, поэтому следите за обновлениями ... – LB2