为什么我无法在SimpleResult主体中组合枚举器



我试图在http://www.playframework.org/documentation/2.0/ScalaStream.

我所做的不同之处在于,我有多个文件要在响应中连接。它看起来像这样:

def index = Action {
val file1 = new java.io.File("/tmp/fileToServe1.pdf")
val fileContent1: Enumerator[Array[Byte]] = Enumerator.fromFile(file1)    
val file2 = new java.io.File("/tmp/fileToServe2.pdf")
val fileContent2: Enumerator[Array[Byte]] = Enumerator.fromFile(file2)   
SimpleResult(
header = ResponseHeader(200),
body = fileContent1 andThen fileContent2
)

}

结果是只有第一个文件的内容在响应中。

像下面这样稍微简单一点的东西可以很好地工作:

fileContent1 = Enumerator("blah" getBytes)
fileContent2 = Enumerator("more blah" getBytes)
SimpleResult(
header = ResponseHeader(200),
body = fileContent1 andThen fileContent2
)

我做错了什么?

我在阅读播放代码时得到了大部分内容,所以我可能误解了,但从我所看到的来看:Enumerator.fromFile函数(Iteratee.scala[docs][src])创建了一个枚举器,当应用于Iteratee[Byte, Byte]时,当它从文件中读取完毕时,会将Input.EOF附加到Iteratee的输出中。

根据剧本!您链接到的docs示例应该手动在枚举器的末尾附加一个Input.EOF(Enumerator.EOF的别名)。我认为,将Input.EOF自动附加到文件字节数组的末尾是只返回一个文件的原因。

游戏控制台中相当简单的例子是:

scala> val fileContent1 = Enumerator("blah") andThen Enumerator.eof
fileContent1: play.api.libs.iteratee.Enumerator[java.lang.String] = play.api.libs.iteratee.Enumerator$$anon$23@2256deba
scala> val fileContent2 = Enumerator(" and more blah") andThen Enumerator.eof
fileContent2: play.api.libs.iteratee.Enumerator[java.lang.String] = play.api.libs.iteratee.Enumerator$$anon$23@7ddeef8a
scala> val it: Iteratee[String, String] = Iteratee.consume[String]()
it: play.api.libs.iteratee.Iteratee[String,String] = play.api.libs.iteratee.Iteratee$$anon$18@6fc6ce97
scala> Iteratee.flatten((fileContent1 andThen fileContent2) |>> it).run.value.get
res9: String = blah

虽然我还没有尝试过,但解决方法是更深入地使用Enumerator.fromCallback函数,并向其传递一个自定义检索器函数,该函数会不断返回Array[Byte]s,直到读取了所有要连接的文件。请参阅fromStream函数的实现,了解默认值是什么以及如何修改它

如何做到这一点的示例(改编自Enumerator.fromStream):

def fromFiles(files: List[java.io.File], chunkSize: Int = 1024 * 8): Enumerator[Array[Byte]] = {
val inputs = files.map { new java.io.FileInputStream(_) }
var inputsLeftToRead = inputs
Enumerator.fromCallback(() => {
def promiseOfChunkReadFromFile(inputs: List[java.io.FileInputStream]): Promise[Option[Array[Byte]]] = {
val buffer = new Array[Byte](chunkSize)
(inputs.head.read(buffer), inputs.tail.headOption) match {
case (-1, Some(_)) => {
inputsLeftToRead = inputs.tail
promiseOfChunkReadFromFile(inputsLeftToRead)
}
case (-1, None) => Promise.pure(None)
case (read, _) => {
val input = new Array[Byte](read)
System.arraycopy(buffer, 0, input, 0, read)
Promise.pure(Some(input))
}
}
}
promiseOfChunkReadFromFile(inputs)
}, {() => inputs.foreach(_.close())})
}

这是我用来一起播放一堆又一堆音频文件的播放框架2.2代码。您可以看到scalaIterator的使用,它被转换为java.io.SequenceInputStream的javaEnumeration

def get_files(name: String) = Action { implicit request =>
import java.lang.IllegalArgumentException
import java.io.FileNotFoundException
import scala.collection.JavaConverters._
try {
val file1 = new java.io.File("songs/phone-incoming-call.ogg")
val file2 = new java.io.File("songs/message.ogg")
val file3 = new java.io.File("songs/desktop-login.ogg")
val inputStream1 = new java.io.FileInputStream(file1)
val inputStream2 = new java.io.FileInputStream(file2)
val inputStream3 = new java.io.FileInputStream(file3)
val it = Iterator(inputStream1, inputStream2, inputStream3)
val seq = new java.io.SequenceInputStream(it.asJavaEnumeration)
val fileContent: Enumerator[Array[Byte]] = Enumerator.fromStream(seq)
val length: Long = file1.length + file2.length + file3.length
SimpleResult(
header = ResponseHeader(200, Map(CONTENT_LENGTH -> length.toString)),
body = fileContent
)
}
catch {
case e: IllegalArgumentException =>
BadRequest("Could not retrieve file. Error: " + e.getMessage)
case e: FileNotFoundException =>
BadRequest("Could not find file. Error: " + e.getMessage)
}
}

我的代码唯一的问题(就我正在寻找的内容而言)是它是一个流,这意味着即使我正在发送CONTENT_LENGTH,我也无法从上一个时间点恢复播放。也许我计算总长度的方法不对。

它的另一个问题是,音频文件需要具有相同的类型(预期)、相同数量的通道和相同的采样率(我花了一段时间才弄清楚),例如,所有音频文件都必须是ogg 44100Hz 2通道。

最新更新