mirror of
https://github.com/yacy/yacy_search_server.git
synced 2025-06-22 04:16:09 -04:00
Render additional embedded audios from links on extended audio search
This commit is contained in:
16
htroot/env/base.css
vendored
16
htroot/env/base.css
vendored
@ -1110,6 +1110,22 @@ div#tagcloud {
|
||||
color: #ff8c00;
|
||||
}
|
||||
|
||||
.embeddedAudios {
|
||||
/* Remove bootstrap ul bottom margin so that there is no interval with the expandable list */
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
/* Button to expand/collapse audio results beyond the initial number of elements display limit */
|
||||
.expandAudiosBtn[aria-expanded="true"] .glyphicon:before {
|
||||
/* Repeated same char as in the glyphicon-chevron-left class */
|
||||
content: "\e079\e079";
|
||||
}
|
||||
|
||||
.expandAudiosBtn[aria-expanded="false"] .glyphicon:before {
|
||||
/* Repeated same char as in the glyphicon-chevron-right class */
|
||||
content: "\e080\e080";
|
||||
}
|
||||
|
||||
/******* yacysearch.html end ***********/
|
||||
|
||||
/******* yacysearchtrailer.html **********
|
||||
|
@ -19,6 +19,24 @@
|
||||
|
||||
/* Functions dedicated to control playing of YaCy audio search results */
|
||||
|
||||
/**
|
||||
* Show elements that are only useful when JavaScript is enabled
|
||||
*/
|
||||
function showJSAudioControls() {
|
||||
var audioElems = document.getElementsByTagName("audio");
|
||||
var audioControls = document.getElementById("audioControls");
|
||||
if(audioElems != null && audioElems.length > 0 && audioControls != null && audioControls.className.indexOf("hidden") >= 0) {
|
||||
audioControls.className = audioControls.className.replace("hidden", "");
|
||||
}
|
||||
|
||||
var expandAudioButtons = document.getElementsByClassName("expandAudiosBtn");
|
||||
if(expandAudioButtons != null) {
|
||||
for(var i = 0; i < expandAudioButtons.length; i++) {
|
||||
expandAudioButtons[i].className = expandAudioButtons[i].className.replace("hidden", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle embedded audio result load error.
|
||||
*
|
||||
@ -28,11 +46,15 @@
|
||||
function handleAudioLoadError(event) {
|
||||
if (event != null && event.target != null) {
|
||||
/* Fill the title attribute to provide some feedback about the error without need for looking at the console */
|
||||
var titleAddition;
|
||||
if (event.target.error != null && event.target.error.message) {
|
||||
event.target.title = "Cannot play ("
|
||||
titleAddition = " - Cannot play ("
|
||||
+ event.target.error.message + ")";
|
||||
} else {
|
||||
event.target.title = "Cannot play";
|
||||
titleAddition = " - Cannot play";
|
||||
}
|
||||
if(event.target.title == null || event.target.title.indexOf(titleAddition) < 0) {
|
||||
event.target.title += titleAddition;
|
||||
}
|
||||
|
||||
/* Apply CSS class marking error for visual feedback*/
|
||||
@ -165,7 +187,7 @@ function playAllNext(playerElem) {
|
||||
while (nextTrack < audioElems.length) {
|
||||
var audioElem = audioElems[nextTrack];
|
||||
if (audioElem != null && audioElem.error == null
|
||||
&& audioElem.play) {
|
||||
&& audioElem.play && audioElem.className.indexOf("hidden") < 0) {
|
||||
if(audioElem.currentTime > 0) {
|
||||
audioElem.currentTime = 0;
|
||||
}
|
||||
@ -177,8 +199,23 @@ function playAllNext(playerElem) {
|
||||
/* Go to the next element when not playable */
|
||||
nextTrack++;
|
||||
}
|
||||
if(nextTrack >= audioElems.length) {
|
||||
/* No other result to play */
|
||||
if (currentTrack >= 0 && currentTrack < audioElems.length
|
||||
&& !audioElems[currentTrack].paused
|
||||
&& audioElems[currentTrack].pause) {
|
||||
audioElems[currentTrack].pause();
|
||||
}
|
||||
playerElem.setAttribute("data-current-track", -1);
|
||||
updatePlayAllButton(false);
|
||||
}
|
||||
} else {
|
||||
/* No other result to play */
|
||||
if (currentTrack >= 0 && currentTrack < audioElems.length
|
||||
&& !audioElems[currentTrack].paused
|
||||
&& audioElems[currentTrack].pause) {
|
||||
audioElems[currentTrack].pause();
|
||||
}
|
||||
playerElem.setAttribute("data-current-track", -1);
|
||||
updatePlayAllButton(false);
|
||||
}
|
||||
@ -224,7 +261,7 @@ function handlePlayAllBtnClick() {
|
||||
var audioElems = document.getElementsByTagName("audio");
|
||||
var playerElem = document.getElementById("audioControls");
|
||||
var playAllIcon = document.getElementById("playAllIcon");
|
||||
if (playerElem != null && audioElems != null) {
|
||||
if (playerElem != null && audioElems != null && playAllIcon != null) {
|
||||
if (playAllIcon.className == "glyphicon glyphicon-play" && audioElems.length > 0) {
|
||||
var currentTrack = parseInt(playerElem
|
||||
.getAttribute("data-current-track"));
|
||||
@ -243,7 +280,7 @@ function handlePlayAllBtnClick() {
|
||||
while (currentTrack < audioElems.length) {
|
||||
var currentAudioElem = audioElems[currentTrack];
|
||||
if (currentAudioElem != null && currentAudioElem.error == null
|
||||
&& currentAudioElem.play) {
|
||||
&& currentAudioElem.play && currentAudioElem.className.indexOf("hidden") < 0) {
|
||||
currentAudioElem.play();
|
||||
updatePlayAllButton(true);
|
||||
break;
|
||||
@ -291,4 +328,80 @@ function handleStopAllBtnClick() {
|
||||
if (playerElem != null) {
|
||||
playerElem.setAttribute("data-current-track", -1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle visibility on a list of audio elements beyond the initial limit of elements to display.
|
||||
* @param {HTMLButtonElement} button the button used to expand the audio elements
|
||||
* @param {String} expandableAudiosId the id of the container of audio elements which visibility has to be toggled
|
||||
* @param {String} hiddenCountId the id of the element containing the number of hidden elements when expandable audios are collapsed
|
||||
* @param {String} evenMoreCountId the id of the eventual element containing the number of hidden elements remaining when audios are expanded
|
||||
*/
|
||||
function toggleExpandableAudios(button, expandableAudiosId, hiddenCountId, evenMoreCountId) {
|
||||
var expandableAudiosContainer = document.getElementById(expandableAudiosId);
|
||||
var hiddenCountElem = document.getElementById(hiddenCountId);
|
||||
var evenMoreElem = document.getElementById(evenMoreCountId);
|
||||
if(button != null && expandableAudiosContainer != null) {
|
||||
var childrenAudioElems = expandableAudiosContainer.getElementsByTagName("audio");
|
||||
var playerElem = document.getElementById("audioControls");
|
||||
var playAllIcon = document.getElementById("playAllIcon");
|
||||
var currentPlayAllTrack = playerElem != null ? parseInt(playerElem
|
||||
.getAttribute("data-current-track")) : -1;
|
||||
if(button.getAttribute("aria-expanded") == "true") {
|
||||
var currentPlayAllAudioElem = null;
|
||||
if(!isNaN(currentPlayAllTrack) && currentPlayAllTrack >= 0) {
|
||||
/* Currently playing all audio results */
|
||||
var audioElems = document.getElementsByTagName("audio");
|
||||
if(audioElems != null && audioElems.length > currentPlayAllTrack) {
|
||||
currentPlayAllAudioElem = audioElems[currentPlayAllTrack];
|
||||
}
|
||||
}
|
||||
/* Additionnaly we modify the aria-expanded state for improved accessiblity */
|
||||
button.setAttribute("aria-expanded", "false");
|
||||
button.title = button.getAttribute("data-title-collapsed");
|
||||
expandableAudiosContainer.className += " hidden";
|
||||
if(hiddenCountElem != null) {
|
||||
hiddenCountElem.className = hiddenCountElem.className.replace("hidden", "");
|
||||
}
|
||||
if(evenMoreElem != null) {
|
||||
evenMoreElem.className += " hidden";
|
||||
}
|
||||
var hidingPlayAll = false;
|
||||
for(var i = 0; i < childrenAudioElems.length; i++) {
|
||||
var audioElem = childrenAudioElems[i];
|
||||
if(currentPlayAllAudioElem == audioElem) {
|
||||
/* Playing all results, and the currently playing element will be hidden*/
|
||||
hidingPlayAll = true;
|
||||
} else if (audioElem.pause && !audioElem.paused) {
|
||||
/* Pause this as it will be hidden */
|
||||
audioElem.pause();
|
||||
}
|
||||
audioElem.className += " hidden";
|
||||
}
|
||||
if(hidingPlayAll) {
|
||||
if (playAllIcon.className == "glyphicon glyphicon-play") {
|
||||
/* Stop playing all */
|
||||
updatePlayAllButton(false);
|
||||
playerElem.setAttribute("data-current-track", -1);
|
||||
} else {
|
||||
/* Continue playing all to an element that is not hidden */
|
||||
playAllNext(playerElem);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Additionnaly we modify the aria-expanded state for improved accessiblity */
|
||||
button.setAttribute("aria-expanded", "true");
|
||||
button.title = button.getAttribute("data-title-expanded");
|
||||
expandableAudiosContainer.className = expandableAudiosContainer.className.replace("hidden", "");
|
||||
if(hiddenCountElem != null) {
|
||||
hiddenCountElem.className += " hidden";
|
||||
}
|
||||
if(evenMoreElem != null) {
|
||||
evenMoreElem.className = evenMoreElem.className.replace("hidden", "");
|
||||
}
|
||||
for(var j = 0; j < childrenAudioElems.length; j++) {
|
||||
childrenAudioElems[j].className = childrenAudioElems[j].className.replace("hidden", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -29,12 +29,7 @@
|
||||
|
||||
#(resultTable)#::::
|
||||
#(embed)#::
|
||||
/* Show the overall audio play control buttons only when JavaScript is enabled as they are useless without it */
|
||||
var audioElems = document.getElementsByTagName("audio");
|
||||
var audioControls = document.getElementById("audioControls");
|
||||
if(audioElems != null && audioElems.length > 0 && audioControls != null && audioControls.className.indexOf("hidden") >= 0) {
|
||||
audioControls.className = audioControls.className.replace("hidden", "");
|
||||
}
|
||||
showJSAudioControls();
|
||||
#(/embed)#
|
||||
#(/resultTable)#
|
||||
|
||||
|
@ -65,13 +65,48 @@
|
||||
<tr class="#(col)#TableCellLight::TableCellDark#(/col)#">
|
||||
<td>#[name]#</td>
|
||||
<td><a href="#[href]#" target="#[target]#" #(noreferrer)#::rel="noreferrer"#(/noreferrer)#>#[hrefshort]#</a>
|
||||
<td class="text-center">#(embed)#::<audio src="#[href]#"
|
||||
preload="none" controls="controls"
|
||||
onplaying="handleAudioPlaying(event)"
|
||||
onpause="handleAudioPause(event)"
|
||||
onerror="handleAudioLoadError(event)"
|
||||
onended="handleAudioEnded(event)">Not supported
|
||||
</audio>#(/embed)#
|
||||
<td class="text-center">#(embed)#::#(list)#::<ul class="embeddedAudios list-unstyled">#(/list)#
|
||||
#{audioSources}#
|
||||
#(list)#::<li>#(/list)#
|
||||
<audio src="#[href]#"
|
||||
title="#[title]#"
|
||||
preload="none" controls="controls"
|
||||
onplaying="handleAudioPlaying(event)"
|
||||
onpause="handleAudioPause(event)"
|
||||
onerror="handleAudioLoadError(event)"
|
||||
onended="handleAudioEnded(event)">Not supported
|
||||
</audio>
|
||||
#(list)#::</li>#(/list)#
|
||||
#{/audioSources}#
|
||||
#(list)#::</ul>#(/list)#
|
||||
#(moreAudios)#::
|
||||
<ul id="expandableAudios_#[urlhash]#" class="list-unstyled hidden">
|
||||
#{audioSources}#<li>
|
||||
<audio src="#[href]#"
|
||||
title="#[title]#"
|
||||
class="hidden"
|
||||
preload="none" controls="controls"
|
||||
onplaying="handleAudioPlaying(event)"
|
||||
onpause="handleAudioPause(event)"
|
||||
onerror="handleAudioLoadError(event)"
|
||||
onended="handleAudioEnded(event)">Not supported
|
||||
</audio></li>#{/audioSources}#
|
||||
</ul>
|
||||
<ul class="list-unstyled">
|
||||
<li>
|
||||
<span id="hiddenCount_#[urlhash]#">#[hiddenCount]# more...</span>
|
||||
<button class="expandAudiosBtn btn btn-default btn-xs hidden" type="button"
|
||||
aria-controls="expandableAudios_#[urlhash]#" aria-expanded="false"
|
||||
title="Show #[expandableCount]# more"
|
||||
data-title-collapsed="Show #[expandableCount]# more"
|
||||
data-title-expanded="Show only the first #[firstLimit]#"
|
||||
onclick="toggleExpandableAudios(this, 'expandableAudios_#[urlhash]#', 'hiddenCount_#[urlhash]#', 'evenMoreCount_#[urlhash]#')">
|
||||
<span class="glyphicon"></span>
|
||||
</button>
|
||||
#(evenMore)#<span id="evenMoreCount_#[urlhash]#" class="hidden">#[count]# more...</span>#(/evenMore)#
|
||||
</li>
|
||||
</ul>
|
||||
#(/moreAudios)##(/embed)#
|
||||
</td>
|
||||
</tr>#(/item)#
|
||||
::
|
||||
|
@ -31,10 +31,13 @@ import java.net.MalformedURLException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import net.yacy.cora.date.GenericFormatter;
|
||||
import net.yacy.cora.date.ISO8601Formatter;
|
||||
@ -461,18 +464,12 @@ public class yacysearchitem {
|
||||
final String resultUrlstring = ms.url().toNormalform(true);
|
||||
final String target = sb.getConfig(resultUrlstring.matches(target_special_pattern) ? SwitchboardConstants.SEARCH_TARGET_SPECIAL : SwitchboardConstants.SEARCH_TARGET_DEFAULT, "_self");
|
||||
prop.putHTML("content_item_href", resultUrlstring);
|
||||
final String mediaType = ms.mime();
|
||||
if (extendedSearchRights && mediaType != null && mediaType.startsWith("audio/")) {
|
||||
/*
|
||||
* Display HTML5 embedded audio only :
|
||||
* - when content-type is known to be audio (each browser has its own set of supported audio subtypes,
|
||||
* so the browser will then handle itself eventual report about unsupported media format)
|
||||
* - to authenticated users with extended search rights to prevent any media redistribution issue
|
||||
*/
|
||||
prop.put("content_item_embed", true);
|
||||
prop.putHTML("content_item_embed_href", resultUrlstring);
|
||||
prop.putHTML("content_item_embed_mediaType", mediaType);
|
||||
} else {
|
||||
if(theSearch.query.contentdom == ContentDomain.AUDIO && extendedSearchRights) {
|
||||
/*
|
||||
* Display HTML5 embedded audio only to authenticated users with extended search rights to prevent any media redistribution issue
|
||||
*/
|
||||
processEmbedAudio(prop, theSearch, ms);
|
||||
}else {
|
||||
prop.put("content_item_embed", false);
|
||||
}
|
||||
prop.put("content_item_noreferrer", noreferrer ? 1 : 0);
|
||||
@ -490,6 +487,126 @@ public class yacysearchitem {
|
||||
return prop;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param prop the target properties
|
||||
* @param theSearch the search event
|
||||
* @param result a result entry
|
||||
*/
|
||||
private static void processEmbedAudio(final serverObjects prop, final SearchEvent theSearch,
|
||||
final URIMetadataNode result) {
|
||||
final String mediaType = result.mime();
|
||||
|
||||
if (mediaType != null && mediaType.startsWith("audio/")) {
|
||||
/*
|
||||
* content-type is known to be audio : each browser has its own set of supported
|
||||
* audio subtypes, so the browser will then handle itself eventual report about
|
||||
* unsupported media format
|
||||
*/
|
||||
prop.put("content_item_embed", true);
|
||||
prop.put("content_item_embed_list", false);
|
||||
prop.put("content_item_embed_audioSources", 1);
|
||||
appendEmbeddedAudio(result, result.url(), prop, "content_item_embed_audioSources_0");
|
||||
prop.put("content_item_embed_audioSources_0_list", false);
|
||||
} else if (result.laudio() > 0 && !theSearch.query.isStrictContentDom()) {
|
||||
/*
|
||||
* The result media type is not audio, but there are some links to audio
|
||||
* resources : render a limited list of embedded audio elements
|
||||
*/
|
||||
final TreeSet<MultiProtocolURL> audioLinks = new TreeSet<>(
|
||||
Comparator.comparing(MultiProtocolURL::getHost).thenComparing(MultiProtocolURL::getFile));
|
||||
final int firstAudioLinksLimit = 3;
|
||||
final int secondAudioLinksLimit = 50;
|
||||
|
||||
filterAudioLinks(URIMetadataNode.getLinks(result, false), audioLinks, result.laudio());
|
||||
filterAudioLinks(URIMetadataNode.getLinks(result, true), audioLinks, result.laudio());
|
||||
|
||||
if (!audioLinks.isEmpty()) {
|
||||
prop.put("content_item_embed", true);
|
||||
final boolean hasMoreThanOne = audioLinks.size() > 1;
|
||||
prop.put("content_item_embed_list", hasMoreThanOne);
|
||||
prop.put("content_item_embed_audioSources", Math.min(audioLinks.size(), firstAudioLinksLimit));
|
||||
final Iterator<MultiProtocolURL> linksIter = audioLinks.iterator();
|
||||
for (int i = 0; linksIter.hasNext() && i < firstAudioLinksLimit; i++) {
|
||||
appendEmbeddedAudio(result, linksIter.next(), prop, "content_item_embed_audioSources_" + i);
|
||||
prop.put("content_item_embed_audioSources_" + i + "_list", hasMoreThanOne);
|
||||
}
|
||||
if (audioLinks.size() > firstAudioLinksLimit) {
|
||||
prop.put("content_item_embed_moreAudios", true);
|
||||
prop.put("content_item_embed_moreAudios_firstLimit", firstAudioLinksLimit);
|
||||
prop.put("content_item_embed_moreAudios_hiddenCount",
|
||||
String.valueOf(audioLinks.size() - firstAudioLinksLimit));
|
||||
prop.put("content_item_embed_moreAudios_expandableCount",
|
||||
String.valueOf(Math.min(audioLinks.size(), secondAudioLinksLimit) - firstAudioLinksLimit));
|
||||
prop.put("content_item_embed_moreAudios_urlhash", ASCII.String(result.hash()));
|
||||
|
||||
prop.put("content_item_embed_moreAudios_audioSources",
|
||||
Math.min(audioLinks.size(), secondAudioLinksLimit) - firstAudioLinksLimit);
|
||||
for (int i = 0; linksIter.hasNext() && i < (secondAudioLinksLimit - firstAudioLinksLimit); i++) {
|
||||
appendEmbeddedAudio(result, linksIter.next(), prop,
|
||||
"content_item_embed_moreAudios_audioSources_" + i);
|
||||
}
|
||||
} else {
|
||||
prop.put("content_item_embed_moreAudios", false);
|
||||
}
|
||||
prop.put("content_item_embed_moreAudios_evenMore", audioLinks.size() > secondAudioLinksLimit);
|
||||
if (audioLinks.size() > secondAudioLinksLimit) {
|
||||
prop.put("content_item_embed_moreAudios_evenMore_count",
|
||||
String.valueOf(audioLinks.size() - secondAudioLinksLimit));
|
||||
prop.put("content_item_embed_moreAudios_evenMore_urlhash", ASCII.String(result.hash()));
|
||||
}
|
||||
} else {
|
||||
prop.put("content_item_embed", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the properties of an embedded audio element to prop. All parameters must not be null.
|
||||
* @param mainResult the result entry to which the audio link belongs
|
||||
* @param audioLink an audio link URL
|
||||
* @param prop the target properties
|
||||
* @param propPrefix the prefix to use when appending prop
|
||||
*/
|
||||
private static void appendEmbeddedAudio(final URIMetadataNode mainResult,
|
||||
final MultiProtocolURL audioLink, final serverObjects prop, final String propPrefix) {
|
||||
prop.putHTML(propPrefix + "_href", audioLink.toString());
|
||||
|
||||
/* Add a title to help user distinguish embedded elements of the list */
|
||||
final String title;
|
||||
if(audioLink.getHost().equals(mainResult.url().getHost())) {
|
||||
/* Inbound link : the file name is sufficient */
|
||||
title = shorten(audioLink.getFileName(), MAX_NAME_LENGTH);
|
||||
} else {
|
||||
/* Outbound link : it may help to know where the file is hosted without having to inspect the html element */
|
||||
title = nxTools.shortenURLString(audioLink.toString(), MAX_URL_LENGTH);
|
||||
}
|
||||
prop.putHTML(propPrefix+ "_title", title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to the target set, valid URLs from the iterator that are classified as
|
||||
* audio from their file name extension.
|
||||
*
|
||||
* @param linksIter an iterator on URL strings
|
||||
* @param target the target set to fill
|
||||
* @param targetMaxSize the maximum target set size
|
||||
*/
|
||||
protected static void filterAudioLinks(final Iterator<String> linksIter, final Set<MultiProtocolURL> target,
|
||||
final int targetMaxSize) {
|
||||
while (linksIter.hasNext() && target.size() < targetMaxSize) {
|
||||
final String linkStr = linksIter.next();
|
||||
try {
|
||||
final MultiProtocolURL url = new MultiProtocolURL(linkStr);
|
||||
if (Classification.isAudioExtension(MultiProtocolURL.getFileExtension(url.getFileName()))) {
|
||||
target.add(url);
|
||||
}
|
||||
} catch (final MalformedURLException ignored) {
|
||||
/* Continue to next link */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to retrieve favicon url from solr result document, or generates
|
||||
* default favicon URL (i.e. "http://host/favicon.ico") from resultURL and
|
||||
|
Reference in New Issue
Block a user