JustAudio/aud_file.cpp

159 lines
4.3 KiB
C++
Raw Normal View History

#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
// <http://www.gnu.org/licenses/>.
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;
}