Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
braheezy committed Aug 6, 2024
1 parent 6724c4c commit 00ac015
Show file tree
Hide file tree
Showing 32 changed files with 1,857 additions and 29 deletions.
4 changes: 2 additions & 2 deletions LICENSE.txt → LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) [year] [fullname]
Copyright (c) [2024] [braheezy]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand All @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
Binary file modified bun.lockb
Binary file not shown.
26 changes: 18 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
{
"main": "./dist/extension.cjs",
"module": "index.ts",
"extensionKind": [
"ui",
"workspace"
],
"dependencies": {
"@types/vscode": "^1.86.0"
"@types/vscode": "^1.86.0",
"vscode-uri": "^3.0.6"
},
"devDependencies": {
"@types/bun": "latest",
Expand All @@ -24,21 +29,26 @@
"//": "START EXTENSION ATTRIBUTES",
"name": "qoa-vscode-extension",
"version": "0.0.1",
"description": "Extension description",
"displayName": "Extension display name",
"description": "Play QOA audio files.",
"displayName": "qoa",
"repository": {
"type": "git",
"url": "https://github.com/YOURNAME"
"url": "https://github.com/braheezy/qoa-vscode-extension"
},
"contributes": {
"commands": [
"customEditors": [
{
"command": "bun-vscode-extension.helloworld",
"title": "Hello World"
"viewType": "qoa-vscode-extension.qoaPreview",
"displayName": "QOAPlayer",
"selector": [
{
"filenamePattern": "*.qoa"
}
]
}
]
},
"engines": {
"vscode": "^1.86.0"
}
}
}
18 changes: 5 additions & 13 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import * as vscode from "vscode";
import * as vscode from 'vscode';
import { registerQOAPreviewSupport } from './qoaPreview';

export const activate = (context: vscode.ExtensionContext) => {
const disposable = vscode.commands.registerCommand(
"bun-vscode-extension.helloworld",
() => {
vscode.window.showInformationMessage("Hello World!");
}
);

context.subscriptions.push(disposable);
};

export const deactivate = () => {};
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(registerQOAPreviewSupport(context));
}
64 changes: 64 additions & 0 deletions src/media/audioPreview.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}

.audio-player {
display: flex;
align-items: center;
background-color: #f1f3f4;
padding: 10px 20px;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
width: 500px;
}

.audio-player button {
background: none;
border: none;
cursor: pointer;
margin: 0 10px;
}

.audio-player img {
width: 24px;
height: 24px;
}

.audio-player .time-display {
margin: 0 10px;
font-size: 14px;
color: #616161;
}

.audio-player .seek-slider {
-webkit-appearance: none;
width: 100%;
height: 5px;
background: #616161;
border: 1px solid #b5b6b7;
border-radius: 5px;
outline: none;
margin: 0 10px;
}

.audio-player .seek-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
background: #333;
border-radius: 50%;
cursor: pointer;
}

.audio-player .seek-slider::-moz-range-thumb {
width: 12px;
height: 12px;
background: #333;
border-radius: 50%;
cursor: pointer;
}
166 changes: 166 additions & 0 deletions src/media/audioPreview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { QOADecoder } from "../qoaDecoder.js";

(async function () {
const playPauseBtn = document.querySelector('.play-pause');
const seekSlider = document.querySelector('.seek-slider');
const timeDisplay = document.querySelector('.time-display');
const playIcon = document.getElementById('play-icon');
const pauseIcon = document.getElementById('pause-icon');

let audioBuffer;
let context;
let source;
let isPlaying = false;
let duration = 0;
let startTime = 0;
let currentTime = 0;
let animationFrameId;

// @ts-ignore
const vscode = acquireVsCodeApi();

function getSettings() {
const element = document.getElementById('settings');
if (element) {
const data = element.getAttribute('data-settings');
if (data) {
return JSON.parse(data);
}
}
throw new Error(`Could not load settings`);
}

const settings = getSettings();

async function fetchAndDecodeAudio() {
try {
const response = await fetch(settings.src); // Replace with your QOA file URL
const buffer = await response.arrayBuffer();

const decoder = new QOADecoder();
const view = new DataView(buffer);
let pos = 0;
decoder.readByte = () => (pos < buffer.byteLength ? view.getUint8(pos++) : -1);
decoder.seekToByte = (position) => { pos = position; };

if (!decoder.readHeader()) {
throw new Error('Failed to read QOA file header');
}

const channels = decoder.getChannels();
const sampleRate = decoder.getSampleRate();
const totalSamples = decoder.getTotalSamples();

const samples = new Int16Array(totalSamples * channels);
let sampleIndex = 0;

while (!decoder.isEnd()) {
const frameSamples = decoder.readFrame(samples.subarray(sampleIndex));
if (frameSamples < 0) {
throw new Error('Failed to read QOA frame');
}
sampleIndex += frameSamples * channels;
}

context = new AudioContext();
audioBuffer = context.createBuffer(channels, samples.length / channels, sampleRate);

for (let i = 0; i < channels; i++) {
const channelData = new Float32Array(samples.length / channels);
for (let j = 0; j < channelData.length; j++) {
channelData[j] = samples[j * channels + i] / 32768;
}
audioBuffer.copyToChannel(channelData, i);
}

duration = audioBuffer.duration;
timeDisplay.textContent = `0:00 / ${formatTime(duration)}`;
seekSlider.max = duration;

} catch (error) {
console.error('Error processing QOA file:', error);
}
}

function formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${minutes}:${secs < 10 ? '0' : ''}${secs}`;
}

function playAudio() {
if (source) {
source.stop();
}
source = context.createBufferSource();
source.buffer = audioBuffer;
source.connect(context.destination);
source.onended = () => {
isPlaying = false;
playIcon.style.display = 'block';
pauseIcon.style.display = 'none';
currentTime = 0;
seekSlider.value = 0;
cancelAnimationFrame(animationFrameId);
};
source.start(0, currentTime);
startTime = context.currentTime - currentTime;
isPlaying = true;
playIcon.style.display = 'none';
pauseIcon.style.display = 'block';
updateProgress();
}

function pauseAudio() {
if (source) {
source.playbackRate.value = 0;
currentTime += context.currentTime - startTime;
}
isPlaying = false;
playIcon.style.display = 'block';
pauseIcon.style.display = 'none';
cancelAnimationFrame(animationFrameId);
}

function updateProgress() {
if (isPlaying) {
currentTime = context.currentTime - startTime;
seekSlider.value = currentTime;
timeDisplay.textContent = `${formatTime(currentTime)} / ${formatTime(duration)}`;
animationFrameId = requestAnimationFrame(updateProgress);
}
}

playPauseBtn.addEventListener('click', () => {
if (isPlaying) {
pauseAudio();
} else {
playAudio();
}
});

seekSlider.addEventListener('input', () => {
currentTime = parseFloat(seekSlider.value);
timeDisplay.textContent = `${formatTime(currentTime)} / ${formatTime(duration)}`;
if (isPlaying) {
source.stop();
playAudio();
}
});

seekSlider.addEventListener('change', () => {
currentTime = parseFloat(seekSlider.value);
timeDisplay.textContent = `${formatTime(currentTime)} / ${formatTime(duration)}`;
if (isPlaying) {
source.stop();
playAudio();
} else {
context.resume().then(() => {
startTime = context.currentTime - currentTime;
source.start(0, currentTime);
});
}
});

await fetchAndDecodeAudio();
})();
9 changes: 9 additions & 0 deletions src/media/menu.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/media/mute.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/media/pause.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/media/play.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/media/volume.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 00ac015

Please sign in to comment.