Twilio は、コンテンツの再生の一時停止と再開をサポートしていますか? 言い換えれば、発信者に再生されるかなり長いファイルがあり、一時停止と再開機能を実装する方法を見つけようとしています。一部のコンテンツの再生中に、ユーザーが数字を押して一時停止し、後でもう一度数字を押して、一時停止したオーディオ ファイルの同じポイントから再生を再開できるようにしたいと考えています。
Twilio はそのようなものをサポートしていますか?
Twilio は、コンテンツの再生の一時停止と再開をサポートしていますか? 言い換えれば、発信者に再生されるかなり長いファイルがあり、一時停止と再開機能を実装する方法を見つけようとしています。一部のコンテンツの再生中に、ユーザーが数字を押して一時停止し、後でもう一度数字を押して、一時停止したオーディオ ファイルの同じポイントから再生を再開できるようにしたいと考えています。
Twilio はそのようなものをサポートしていますか?
Twilio で再生を一時停止および再開する方法について、完全ではありませんが実行可能な解決策を見つけました。
Gather
基本的な考え方は、再生コマンドが生成されたときとURL が呼び出されたときの時間の差を計算することです。違いは (一瞬完璧な世界を想定して)、発信者によって中断される前にコンテンツがどれだけ再生されたかということです。次に、呼び出し元が再開する準備ができたらPlay
、アプリ サーバーが完全なコンテンツを配信するのではなく、再生を再開する必要がある時点から部分的にオフセットされたコンテンツを配信するようにコマンドを生成します (これは、配信するメカニズムがオーディオ ファイル コンテンツの一部のみを実装する必要があります)。これは基本的に一時停止/再開機能をエミュレートします。
私はこれを実装しましたが、多かれ少なかれ機能します。ネットワークの遅延、処理の遅延 (Twilio がPlay
コマンドを受信してから再生リソースを取得し、実際に再生を開始するまでの時間)、およびボタンを押してから実際にGather
通話を受信するまでの遅延のすべてが精度に影響する不完全な世界が登場します。ただし、要件が厳しすぎない場合、ほとんどの場合、精度はおそらく十分です。
これは、私が C# で行った概念実証です (数か月経ちましたが、投稿どおりに機能することを願っています)。また、早送りと巻き戻しの実験も含まれています。これは、再開が実際に開始する場所を調整するだけです (Pause
コマンドをスキップします)。
Play
以下のコードは、 、Pause
、およびその他のコマンドでTwiML を生成する PausablePlayController.cs のコードです。
Play
action (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;
}
}