iosvue.jshtml5-audioaudio-streaming

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


I need some help with my web page. I stream wav files and only on Apple's software music don't play. I was looking for solution in Internet (other peoples problems) and tried different solutions, but without success. Maybe the best would be to examine my web site: https://sidcloud.net. I use html audio html element with my controls. It seems the streaming is starting (from back-end perspective), but audio html element don't play it. Can anyone help and examine the web site? Thanks in advance! Bartek


Solution

  • 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.