package ONO::Lib::Audio::Recorder;
################################################################################
# COPYRIGHT / LICENSE #
################################################################################
#
# This file is part of the ONO Software Project.
#
# Copyright (C) 2000-2025 Jos KIRPS [ www.kirps.com | jos_AT_kirps_DOT_com ]
# and The Joopita Project [ www.joopita.org | contact_AT_joopita_DOT_com ]
#
# This file, as well as other parts of the ONO Software Project or related
# elements, are FREE SOFTWARE available under the ARTISTIC LICENSE 2.0.
#
# This program 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.
#
# For the full license, see /ono/osr/license/LICENSE.txt, or write to
# jos_AT_kirps_DOT_com or contact_AT_joopita_DOT_com.
#
################################################################################
# END OF COPYRIGHT / LICENSE, HERE COMES THE CODE ... #
################################################################################
use strict;
use ONO::IO;
use ONO::Lib::Basic;
###############################################################################
# audio recorder
###############################################################################
sub tmp_file {
my (
$self,
$ymdhms,
$vars_ref,
) = @_;
#: Return the path for a tmp file.
my %vars = %$vars_ref;
my $DIR = "media/tmp/audiorecorder/-";
if ($vars{'username'}) {
$DIR = "media/tmp/audiorecorder/$vars{'username'}";
}
ONO::IO->mkpath("$DIR");
my $filename = substr($vars{'ono_recorder_rec_filename'},0,32);
$filename = lc ONO::Lib::Basic->umlaut_simplify($filename,2);
return "$DIR/$ymdhms-$filename.mp3";
}
sub tmp_files {
#: List tmp files.
my @FILES;
foreach my $file (sort ONO::IO->ls("media/tmp/audiorecorder/$_[1]")) {
if ($file =~ /\.mp3/) {
@FILES = (@FILES,$file);
}
}
return @FILES;
}
sub tmp_garbage {
#: Clean up tmp garbage.
my $time = $_[1];
$time = $time - 7;
$time = "${time}000000";
if (ONO::IO->devstation || int(rand(100)) == 50) {
foreach my $dir (ONO::IO->ls("media/tmp/audiorecorder")) {
if ($dir =~ /[a-z0-9\-]/ && $dir !~ /\./) {
my $count;
foreach my $file (ONO::IO->ls("media/tmp/audiorecorder/$dir")) {
if ($file =~ /^(.*?)-(.*)\.mp3$/) {
$count++;
if ($1 < $time) {
ONO::IO->rm("media/tmp/audiorecorder/$dir/$file");
}
}
}
if (!$count) {
ONO::IO->rmdir("media/tmp/audiorecorder/$dir");
}
}
}
}
}
sub save {
my (
$self,
$FILE,
$DATA,
$switches,
) = @_;
#: Save a recoding
#:
#: -V overwrite existing file
my $success;
if ($FILE && $DATA) {
eval "use MIME::Base64";
if (!$@) {
my ($dir,$file) = ONO::IO->getdirfileext($FILE);
$file = lc $file;
$file =~ s~\.mp3$~~;
$file =~ s~( |-)~_~g;
$file =~ s~[^a-z0-9\_]~~g;
if (!$file) {
$file = "untitled";
}
ONO::IO->mkpath($dir);
my $filename = "$file.mp3";
my $filebase = $file;
my $whilecounter;
while ($switches !~ /V/ && (ONO::IO->exists($FILE) || ONO::IO->exists("$dir/$filebase.mp3") || ONO::IO->exists("$dir/$filebase.wav")) && $whilecounter < 1000) {
$whilecounter++;
$filename = "${file}_$whilecounter.mp3";
$filebase = "${file}_$whilecounter";
$FILE = "$dir/$filename";
}
$DATA =~ s~^data:audio/mp3;base64,//vkxAA(.*?)//vkxAA(.*?)//vkxAA(.*?)//vkxAA~//vkxAA~;
ONO::IO->dump($FILE,MIME::Base64::decode($DATA));
$success = $filename;
}
}
return $success;
}
sub recorder {
my (
$self,
$ID,
$BLK_ref,
$switches,
$filename
) = @_;
#: The ONO Audio Recorder.
#:
#: -C close/cancel button (simply hides the entire recorder)
#: -f filename
#: -J embed JavaScript
#: -L login required (works with -sS)
#: -s save button
#: -S save disabled
#: -u transmit unique ID as 'ono_recorder_unique_id'
my %BLK = %$BLK_ref;
my ($FILENAME,$SAVE,$CLOSE,$SCRIPT);
if ($switches =~ /C/) {
$CLOSE = qq~<div class="fr ml5 cursorlink" onclick="onojs_hide('ono_lib_audio_recorder_$ID');"><img src="/ono/osr/images/icons/crystal/32x32/ono/close.png" class="close" alt="close"></div>~;
}
if ($switches =~ /u/) {
$SCRIPT .= qq~<input type="hidden" name="ono_recorder_unique_id" value="$ID">~;
}
if ($switches =~ /J/) {
$SCRIPT .= &script("",$ID);
}
if ($switches =~ /s/) {
$SAVE = qq~<input type="submit" name="recorder_${ID}_upload" value="$BLK{'save'}" class="button_green ml5">~;
if ($switches =~ /S/) {
$SAVE = qq~<div class="button_green ml5 trans60">$BLK{'save'}</div>~;
}
$SAVE = qq~<td>
<div id="recorder_${ID}_upload1_div" class="trans40"><div class="button_green ml5">$BLK{'save'}</div></div>
<div id="recorder_${ID}_upload2_div" class="hide">$SAVE</div>
</td>
~;
}
if ($switches =~ /f/) {
if (!$filename) {
$filename = $BLK{'untitled'};
}
my $NAME = qq~<td class="w100"><input type="text" name="ono_recorder_${ID}_filename" value="$filename" class="w95"></td><td>.mp3</td>~;
if ($switches =~ /S/) {
$NAME = qq~<td class="w100"></td>~;
if ($switches =~ /L/) {
$NAME = qq~<td class="w100 italic lightred">login required to save file</td>~;
}
}
$FILENAME = qq~<table id="recorder_${ID}_filename_div" class="wide_table mt5 hide">
<tr>
$NAME
$SAVE
</tr>
</table>
~;
$SAVE = "";
}
if (ONO::IO->https) {
return qq~<div id="ono_lib_audio_recorder_$ID">
<div class="inline auto" style="min-width:400px">
<div class="box_darkblack">
<table class="wide_table">
<tr class="vtop">
<td class="p0"><img class="block64" src="/ono/osr/images/icons/crystal/64x64/apps/krec.png" alt=""></td>
<td class="w100 p0">
$CLOSE
<div class="bold fr" id="recorder_${ID}_clock">0:00</div>
<div class="bold fr lightgreen hide" id="recorder_${ID}_clockdone">0:00</div>
<div class="bold mb5">Audio Recorder</div>
<table class="wide_table">
<tr class="vtop">
<td><div id="recorder_${ID}_start_div"><a id="recorder_${ID}_start" href="javascript:void(0);" onclick="ono_recorder_startRecording();" class="button_green">REC</a></div></td>
<td><div id="recorder_${ID}_stop_div" class="trans40"><a id="recorder_${ID}_stop" href="javascript:void(0);" onclick="ono_recorder_stopRecording();" class="button_red">STOP</a></div></td>
<td class="w100"><div id="ono_recorder_${ID}_playlist"></div></td>
$SAVE
</tr>
</table>
$FILENAME
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="hide">
<input type="hidden" name="ono_recorder_${ID}_id" value="$ID">
<textarea id="ono_recorder_${ID}_data_id" name="ono_recorder_${ID}_data" value="" class="w100 h50"></textarea>
</div>
<input type="hidden" id="recorder_${ID}_clock_run" name="recorder_${ID}_clock_run_value" value="1">
$SCRIPT
~;
} else {
return qq~<div id="ono_lib_audio_recorder_$ID">
<div class="inline auto" style="min-width:400px">
<div class="box_red lightred">
Error: Online AudioRecorder requires a HTTPS / SSL connection!
</div>
</div>
</div>
~;
}
}
sub script {
#: The ONO Audio Recorder JS code.
my $ID = $_[1];
return qq~<script src="/ono/osr/javascript/audiorecorder/v1/dist/index.js"></script>
<script>
const recorder = new MicRecorder({bitRate: 320});
function ono_recorder_startRecording() {
recorder.start().then(() => {
onojs_class('recorder_${ID}_start_div','trans40');
onojs_class('recorder_${ID}_stop_div','');
if (document.getElementById('ono_recorder_${ID}_recdiv')) {
const div = document.getElementById('ono_recorder_${ID}_recdiv');
document.getElementById('ono_recorder_${ID}_playlist').removeChild(div);
}
onojs_hide('recorder_${ID}_clockdone');
onojs_block('recorder_${ID}_clock');
onojs_setvalue('recorder_${ID}_clock_run',1);
onojs_clock_countup('recorder_${ID}_clock',0);
onojs_class('recorder_${ID}_clock','bold fr lightred');
}).catch((e) => {console.error(e)});
}
function ono_recorder_stopRecording() {
recorder.stop().getMp3().then(([buffer, blob]) => {
onojs_class('recorder_${ID}_start_div','');
onojs_class('recorder_${ID}_start','button_yellow');
onojs_class('recorder_${ID}_stop_div','trans40');
onojs_block('recorder_${ID}_filename_div');
onojs_block('recorder_${ID}_upload2_div');
onojs_hide('recorder_${ID}_upload1_div');
onojs_block('recorder_${ID}_clockdone');
onojs_hide('recorder_${ID}_clock');
var counttime = onojs_gethtml('recorder_${ID}_clock');
onojs_sethtml('recorder_${ID}_clockdone',counttime);
onojs_setvalue('recorder_${ID}_clock_run',0);
const file = new File(buffer, 'record.mp3', {type: blob.type,lastModified: Date.now()});
const div = document.createElement('div');
div.id = 'ono_recorder_${ID}_recdiv';
const player = new Audio(URL.createObjectURL(file));
player.controls = true;
player.style.width = '200px';
div.appendChild(player);
document.getElementById('ono_recorder_${ID}_playlist').appendChild(div);
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function() {document.getElementById('ono_recorder_${ID}_data_id').value=reader.result};
}).catch((e) => {console.error(e)});
}
</script>
~;
}
###############################################################################
# end of script
###############################################################################
1;
__END__