[Vuejs]-HTML <audio> stream don't play on Apple's iOS Safari and iPhone – https://sidcloud.net

0👍

Got it working \o/. I had to handle bytes ranges correctly. Apple’s software use range feature, so I had to satisfy its needs :). Here is my REST API function in Golang/GIN:

func AudioGet(c *gin.Context) {

// Typ połączania
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Connection", "Keep-Alive")
c.Header("Transfer-Encoding", "identity")
c.Header("Accept-Ranges", "bytes")

// Odczytujemy parametr - numer muzy
id := c.Param("id")
filenameWAV := cacheDir + id + ".wav"

var size int64
if fileExists(filenameWAV) {
    s, err := fileSize(filenameWAV)
    if ErrCheck(err) {
        log.Println("[GIN:AudioGet] Size of file " + filenameWAV + " = " + strconv.Itoa(int(s)))
        size = s
    } else {
        log.Println("[GIN:AudioGet] Can't read size of file " + filenameWAV)
        c.JSON(http.StatusInternalServerError, "Can't read size of file")
        return
    }
} else {
    log.Println("[GIN:AudioGet] No WAV file " + filenameWAV)
    c.JSON(http.StatusInternalServerError, "No WAV file")
    return
}

//
// Analiza nagłówka - ile bajtów mamy wysłać
//
bytesToSend := 0
bytesToSendStart := 0
bytesToSendEnd := 0
headerRange := c.GetHeader("Range")
log.Println("[GIN:AudioGet2] Header:Range = " + headerRange)
if len(headerRange) > 0 {
    headerRangeSplitted1 := strings.Split(headerRange, "=")

    if len(headerRangeSplitted1) > 0 {
        log.Println("[GIN:AudioGet2] range in " + headerRangeSplitted1[0])

        if len(headerRangeSplitted1) > 1 {
            headerRangeSplitted2 := strings.Split(headerRangeSplitted1[1], "-")
            if len(headerRangeSplitted2) > 0 {
                log.Println("[GIN:AudioGet2] start = " + headerRangeSplitted2[0])
                if len(headerRangeSplitted2) > 1 {
                    log.Println("[GIN:AudioGet2] end = " + headerRangeSplitted2[1])
                    bytesToSendStart, err := strconv.Atoi(headerRangeSplitted2[0])
                    if ErrCheck2(err) {
                        bytesToSendEnd, err := strconv.Atoi(headerRangeSplitted2[1])
                        if ErrCheck2(err) {
                            bytesToSend = bytesToSendEnd - bytesToSendStart + 1
                        }
                    }
                }
            }
        }
    }
}

log.Println("[GIN:AudioGet2] Bytes to send " + strconv.Itoa(bytesToSend))
log.Println("[GIN:AudioGet2] From " + strconv.Itoa(bytesToSendStart) + " to " + strconv.Itoa(bytesToSendEnd))

if bytesToSend > 0 {
    c.Header("Content-length", strconv.Itoa(bytesToSend))
    c.Header("Content-range", "bytes "+strconv.Itoa(bytesToSendStart)+"-"+strconv.Itoa(bytesToSendEnd)+"/"+strconv.Itoa(int(size)))
    size = int64(bytesToSend)
}

// Streaming LOOP...
// ----------------------------------------------------------------------------------------------

// Otwieraamy plik - bez sprawdzania błędów
file, err := os.Open(filenameWAV)
defer file.Close()
if ErrCheck(err) {
    // Info o wejściu do GET
    log.Println("[GIN:AudioGet] Sending " + id + "...")

    p := make([]byte, size)
    file.ReadAt(p, int64(bytesToSendStart))
    file.Close()
    if bytesToSend > 0 {
        c.Data(http.StatusPartialContent, "audio/wav", p)
    } else {
        c.Data(http.StatusOK, "audio/wav", p)
    }
} else {
    log.Println("[GIN:AudioGet] Can't open file " + filenameWAV)
}

}

and here is log:

2020/05/04 14:28:59 [GIN:AudioGet] Size of file cache/190651.wav = 26460044
2020/05/04 14:28:59 [GIN:AudioGet2] Header:Range = bytes=0-1
2020/05/04 14:28:59 [GIN:AudioGet2] range in bytes
2020/05/04 14:28:59 [GIN:AudioGet2] start = 0
2020/05/04 14:28:59 [GIN:AudioGet2] end = 1
2020/05/04 14:28:59 [GIN:AudioGet2] Bytes to send 2
2020/05/04 14:28:59 [GIN:AudioGet2] From 0 to 0
2020/05/04 14:28:59 [GIN:AudioGet] Sending 190651...
2020/05/04 14:29:00 [GIN:AudioGet] Size of file cache/190651.wav = 26460044
2020/05/04 14:29:00 [GIN:AudioGet2] Header:Range = bytes=0-65535
2020/05/04 14:29:00 [GIN:AudioGet2] range in bytes
2020/05/04 14:29:00 [GIN:AudioGet2] start = 0
2020/05/04 14:29:00 [GIN:AudioGet2] end = 65535
2020/05/04 14:29:00 [GIN:AudioGet2] Bytes to send 65536
2020/05/04 14:29:00 [GIN:AudioGet2] From 0 to 0
2020/05/04 14:29:00 [GIN:AudioGet] Sending 190651...
2020/05/04 14:29:00 [GIN:AudioGet] Size of file cache/190651.wav = 26460044
2020/05/04 14:29:00 [GIN:AudioGet2] Header:Range = bytes=26411008-26460043
2020/05/04 14:29:00 [GIN:AudioGet2] range in bytes
2020/05/04 14:29:00 [GIN:AudioGet2] start = 26411008
2020/05/04 14:29:00 [GIN:AudioGet2] end = 26460043
2020/05/04 14:29:00 [GIN:AudioGet2] Bytes to send 49036
2020/05/04 14:29:00 [GIN:AudioGet2] From 0 to 0
2020/05/04 14:29:00 [GIN:AudioGet] Sending 190651...
2020/05/04 14:29:01 [GIN:AudioGet] Size of file cache/190651.wav = 26460044
2020/05/04 14:29:01 [GIN:AudioGet2] Header:Range = bytes=65536-26411007
2020/05/04 14:29:01 [GIN:AudioGet2] range in bytes
2020/05/04 14:29:01 [GIN:AudioGet2] start = 65536
2020/05/04 14:29:01 [GIN:AudioGet2] end = 26411007
2020/05/04 14:29:01 [GIN:AudioGet2] Bytes to send 26345472
2020/05/04 14:29:01 [GIN:AudioGet2] From 0 to 0
2020/05/04 14:29:01 [GIN:AudioGet] Sending 190651...

So, as You can see Apple browsers are asking for first two bytes first, next c.a. 64kB and then the rest of music file.

0👍

I found this while struggling with the same issue and really wasted a lot of time on it! I started to implement this myself too, following your example (thanks for that!) but in the end really luckily came across the fact that GoLang’s http library can actually do this for you. So you can do either:

func AudioGet(c *gin.Context) {
    id := c.Param("id")
    filenameWAV := cacheDir + id + ".wav"
    file := goGetFile(filenameWAV) // assuming goGetFile returns []byte
    http.ServeContent(c.Writer, c.Request, "recording.wav", time.Now(), bytes.NewReader(file))
}

or, if the file is on the filesystem, even:

func AudioGet(c *gin.Context) {
    id := c.Param("id")
    filenameWAV := cacheDir + id + ".wav"
    http.ServeFile(c.Writer, c.Request, filenameWAV) // assuming filenameWAV is the location
}

Here’s the documentation for it: https://golang.org/pkg/net/http/#ServeContent (ServeFile is directly beneath it)

Leave a comment