To facilitate the annotation of audio files in a Google spreadsheet, I'd like to implement an audio player in the sidebar which automatically plays the audio file mentioned as URL in the row of a table. After listening and entering some date in this row, I'd like to move to the next row and do the same. Thus, the URL to the audio file should be updated whenever I select a new row and the whole process should be fast, too, in order to listen quickly to one sound file after the other.
I've experimented with the solution mentioned in this SO post, but this solution is relying on a poll function with a time interval, which is impractical for me as it periodically is updating the sidebar. Crucial for me would be to update the content of the sidebar only once.
Code.gs
var SIDEBAR_TITLE = 'Opnam lauschteren';
/**
* Adds a custom menu with items to show the sidebar and dialog.
*
* @param {Object} e The event parameter for a simple onOpen trigger.
*/
function onOpen(e) {
SpreadsheetApp.getUi()
.createAddonMenu()
.addItem('Opname lauschteren', 'showSidebar')
.addToUi();
}
/**
* Runs when the add-on is installed; calls onOpen() to ensure menu creation and
* any other initializion work is done immediately.
*
* @param {Object} e The event parameter for a simple onInstall trigger.
*/
function onInstall(e) {
onOpen(e);
}
/**
* Opens a sidebar. The sidebar structure is described in the Sidebar.html
* project file.
*/
function showSidebar() {
var ui = HtmlService.createTemplateFromFile('Sidebar')
.evaluate()
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setTitle(SIDEBAR_TITLE);
SpreadsheetApp.getUi().showSidebar(ui);
}
function getValues() {
var app = SpreadsheetApp;
var value = app.getActiveSpreadsheet().getActiveSheet().getActiveCell().getValue();
Logger.log(value);
return value;
}
function getRecord() {
// Retrieve and return the information requested by the sidebar.
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var headers = data[0];
var rowNum = sheet.getActiveCell().getRow();
if (rowNum > data.length) return [];
var record = [];
for (var col=0;col<headers.length;col++) {
var cellval = data[rowNum-1][col];
// Dates must be passed as strings - use a fixed format for now
if (typeof cellval == "object") {
cellval = Utilities.formatDate(cellval, Session.getScriptTimeZone() , "M/d/yyyy");
}
// TODO: Format all cell values using SheetConverter library
record.push({ heading: headers[col],cellval:cellval });
}
Logger.log(record);
return record;
}
Sidebar.html
<!-- Use a templated HTML printing scriptlet to import common stylesheet. -->
<?!= HtmlService.createHtmlOutputFromFile('Stylesheet').getContent(); ?>
<!-- Below is the HTML code that defines the sidebar element structure. -->
<div class="sidebar branding-below">
<!-- The div-table class is used to make a group of divs behave like a table. -->
<div class="block div-table" id="sidebar-record-block">
</div>
<div class="block" id="sidebar-button-bar">
</div>
<div id="sidebar-status"></div>
<!-- Use a templated HTML printing scriptlet to import JavaScript. -->
<?!= HtmlService.createHtmlOutputFromFile('SidebarJavaScript').getContent(); ?>
</div>
<!-- Enter sidebar bottom-branding below. -->
<div class="sidebar bottom">
<span class="gray branding-text">PG</span>
</div>
SidebarJavaScript.html
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
/**
* Run initializations on sidebar load.
*/
$(function() {
// Assign handler functions to sidebar elements here, if needed.
// Call the server here to retrieve any information needed to build
// the dialog, if necessary.
// Start polling for updates
poll();
});
/**
* Poll a server-side function at the given interval, to have
* results passed to a successHandler callback.
*
* https://stackoverflow.com/a/24773178/1677912
*
* @param {Number} interval (optional) Time in ms between polls.
* Default is 2s (2000ms)
*/
function poll(interval) {
interval = interval || 3000;
setTimeout(function() {
google.script.run
.withSuccessHandler(showRecord)
.withFailureHandler(
function(msg, element) {
showStatus(msg, $('#button-bar'));
element.disabled = false;
})
.getRecord();
}, interval);
};
/**
* Callback function to display a "record", or row of the spreadsheet.
*
* @param {object[]} Array of field headings & cell values
*/
function showRecord(record) {
if (record.length) {
for (var i = 2; i <= 2; i++) {
// build field name on the fly, formatted field-1234
var str = '' + i;
var fieldId = 'field-' + ('0000' + str).substring(str.length)
// If this field # doesn't already exist on the page, create it
if (!$('#'+fieldId).length) {
var newField = $($.parseHTML('<div id="'+fieldId+'"></div>'));
$('#sidebar-record-block').append(newField);
}
// Replace content of the field div with new record
$('#'+fieldId).replaceWith('<div id="'+fieldId+'" class="div-table-row"></div>');
$('#'+fieldId).append($('<div class="div-table-th">' + record[i].heading + '</div>'))
.append('<audio id="player" controls > <source src=' + record[i].cellval + ' type=audio/wav > Your browser does not support the audio element. </audio>');
}
}
// TODO: hide any existing fields that are beyond the current record length
//Setup the next poll
poll();
}
/**
* Displays the given status message in the sidebar.
*
* @param {String} msg The status message to display.
* @param {String} classId The message type (class id) that the message
* should be displayed as.
*/
function showStatus(msg, classId) {
$('#sidebar-status').removeClass().html(msg);
if (classId) {
$('#sidebar-status').addClass(classId);
}
}
</script>
A reproducible example is accessible here; Add-ons > 'play audio' (Google account necessary).
I am struggling finding a method to trigger the update of the sidebar only once and only when a new row is selected. The use of a sidebar is not mandatory, rather another solution, e.g. with a automatically updated 'Play' button, would be helpful, too.