#include "aud_file.h" // This file is part of JustAudio. // JustAudio is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // JustAudio is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with BashWire under the GPL.txt file. If not, see // . AudFile::AudFile(QObject *parent) : QFile(parent) { // QMediaPlayer doesn't handle files with an ID3 tag and variable bitrates that well so // this class was created to offset the ID3 tags without modifying the audio files and // to have the GUI rely on the actual byte seek position of the audio track rather than // the time read by QMediaPlayer. offset = 0; v1TagSize = 0; activeInterupt = false; timer = new QTimer(this); timer->setSingleShot(true); connect(timer, SIGNAL(timeout()), this, SIGNAL(endOfPlayback())); } AudFile::~AudFile() { close(); } qint64 AudFile::audSize() { return size() - offset - v1TagSize; } qint64 AudFile::getOffset() { return offset; } bool AudFile::openFile(const QString &path) { close(); v1TagSize = 0; offset = 0; activeInterupt = false; timer->stop(); setFileName(path); bool ret = open(QFile::ReadOnly); if (ret) { seek((size() - 1) - 128); if (peek(3) == "TAG") { // find an ID3v1 tag if it has any. the tag always start with "TAG" and appended // to the end of the file with a fixed length of 128 bytes. v1 tags don't affect // playback with QMediaPlayer but this info will still be needed in audSize(). v1TagSize = 128; } seek(0); if (peek(3) == "ID3") { // find the ID3v2 tag if it has any and set the file offset position to skip over it. // the tag has a 10byte header that starts with "ID3" and ends with a 4 byte // big endian integer that indicates the tag size. QByteArray header = read(10); QByteArray intBytes = header.mid(6); quint64 num = 0; quint64 bit = 1; offset += 10; // skip header if (header[5] & 16) { // footer flag check. if the tag has a footer, an additional 10bytes need // to be added to the offset. offset += 10; // skip footer } for (int i = intBytes.size() - 1; i >= 0; --i) { // read the integer bit-by-bit, setting the bits in "num" as it goes. note // that its reading the bytes from "right to left" for big endian format. int byte = intBytes[i]; for (int inBit = 1; inBit <= 64; inBit *= 2, bit *= 2) { if ((byte & inBit) != 0) num |= bit; } } offset += num; // skip tag } seek(0); } return ret; } bool AudFile::seek(qint64 off) { // QMediaPlayer constantly seeks the audio file for data while playing. each seek // resets the endOfPlayback() timer. if that timer is allowed to timeout, that is // considered finished playback. the player also tend to do another seek after // being paused so activeInterupt is there to prevent the timer from restarting // unexpectly after userInterupt() is called. if (!activeInterupt) { timer->stop(); timer->start(5000); } emit bytePos(pos()); return QFile::seek(offset + off); } void AudFile::userInterupt() { // the timer needs to be aware if the user paused or stopped playback. this way // the endOfPlayback() signal is not called unexpectedly. activeInterupt = true; timer->stop(); } void AudFile::userResume() { // after a pause, this class will also need to be aware of a user resume to remove // the activeInterupt status. activeInterupt = false; }