I'm building a music player and I want to give windows information like the current song's title and artist, and the album art in the popup in the attached picture. It currently display's the song name by changing the <title/> of the page.
Using Electron, HTML, and JavaScript, is this possible?
(Please ask if you need to see any code, I'm not sure what'd be helpful and what would just clutter the question so just ask)
assigned vars are:
songName - songs name,
albumName - albums name,
artistName - artists name,
res - album art,
lyrics - songs lyrics
Yes, this is posible.
You can do this using Media Session API.
Media Session API allows a web page to provide custom behaviors for standard media playback interactions, and to report metadata that can be sent by the user agent to the device or operating system for presentation in standardized user interface elements.
Plus, you can take control about play, pause, previous track and next track buttons.
You can read more about it in next link:
https://developer.mozilla.org/en-US/docs/Web/API/MediaSession
This is a little example:
var trackElement = document.getElementById("track_el");
// Make sure browser has Media Session API available
if ('mediaSession' in navigator) {
// Access to Media Session API
var ms = navigator.mediaSession;
// Create track info JSON variable
var trackInfo = {};
// Set track title
trackInfo.title = "Polaris";
// Set artist name
trackInfo.artist = "Downtown Binary & The Present Sound";
// Set album name
trackInfo.album = "Umbra";
// Set album art (NOTE: image files must be hosted in "http" or "https" protocol to be shown)
trackInfo.artwork = [
{ src: 'https://antonyhr.neocities.org/temp/polaris/polaris_album_art_96.jpg', sizes: '96x96', type: 'image/jpg' },
{ src: 'https://antonyhr.neocities.org/temp/polaris/polaris_album_art_128.jpg', sizes: '128x128', type: 'image/jpg' },
{ src: 'https://antonyhr.neocities.org/temp/polaris/polaris_album_art_192.jpg', sizes: '192x192', type: 'image/jpg' },
{ src: 'https://antonyhr.neocities.org/temp/polaris/polaris_album_art_256.jpg', sizes: '256x256', type: 'image/jpg' },
{ src: 'https://antonyhr.neocities.org/temp/polaris/polaris_album_art_384.jpg', sizes: '384x384', type: 'image/jpg' },
{ src: 'https://antonyhr.neocities.org/temp/polaris/polaris_album_art_512.jpg', sizes: '512x512', type: 'image/jpg' }
];
// Then, we create a new MediaMetadata and pass our trackInfo JSON variable
var mediaMD = new MediaMetadata(trackInfo);
// We assign our mediaMD to MediaSession.metadata property
ms.metadata = mediaMD
// And that will be all for show our custom track info in Windows (or any supported) Media Player Pop-Up
// If we need to customize Media controls, we must set action handlers (NOTE: It's not necessary to add all action handlers).
ms.setActionHandler('play', function() {
trackElement.play();
var trackInfoEl = document.getElementById("track_info_el");
trackInfoEl.textContent = "Track is playing.";
});
ms.setActionHandler('pause', function() {
trackElement.pause();
var trackInfoEl = document.getElementById("track_info_el");
trackInfoEl.textContent = "Track is paused.";
});
ms.setActionHandler('stop', function() { /* Code excerpted. */ });
ms.setActionHandler('seekbackward', function() { /* Code excerpted. */ });
ms.setActionHandler('seekforward', function() { /* Code excerpted. */ });
ms.setActionHandler('seekto', function() { /* Code excerpted. */ });
ms.setActionHandler('previoustrack', function() { /* Code excerpted. */ });
ms.setActionHandler('nexttrack', function() { /* Code excerpted. */ });
} else {
console.warn("Your browser doesn't have Media Session API");
}
body {background: #181818;}
p {color: #fff; font-family: "Verdana", "Roboto", "Century Gothic";}
<audio id="track_el" controls autoplay src="https://firebasestorage.googleapis.com/v0/b/tempfiles-2912.appspot.com/o/polaris_clip.mp3?alt=media&token=bcfc245e-dc89-4046-b287-b99046c786bf" type="audio/mp3"></audio>
<p id="track_info_el">Track is playing.</p>
<p>Once audio is playing, see Media Player Pop-Up.</p>
I'm not pretty sure how you can add Lyrics to Media Player Pop-Up, but I think everything else is covered ✅.
If this answer was helpful, please, don't doubt to vote up.
Have a nice coding :D
Related
I have created hero card for messages with prompt buttons from qna maker. The hero card embedded response has a title and buttons. The buttons are displayed properly and worked as expected, but the title words are not wrapped properly.
if (resResult) {
var answer = resResult.answer;
var resultContext = resResult.context;
var prompts = resultContext && resultContext.prompts;
if (prompts && prompts.length) {
var card = CardFactory.heroCard(
answer,
[],
prompts.map(prompt => ({
type: 'messageBack',
title: prompt.displayText,
displayText: prompt.displayText,
text: prompt.displayText,
value: {
qnaId: prompt.qnaId
}
}))
);
answer = MessageFactory.attachment(card);
}
await context.sendActivity(answer);
}
The output response in chat window / Emulator is
The title text which is displayed needs to wrapped and font style and color should align with common text styles of chat bot.
Thanks in advance
By default App Insights use page title as event name. Having dynamic page names, like "Order 32424", creates insane amount of event types.
Documentation on the matter says to use trackEvent method, but there are no examples.
appInsights.trackEvent("Edit button clicked", { "Source URL": "http://www.contoso.com/index" })
What is the best approach? It would be perfect to have some sort of map/filter which would allow to modify event name for some pages to the shared name, like "Order 23424" => "Order", at the same time to leave most pages as they are.
You should be able to leverage telemetry initializer approach to replace certain pattern in the event name with the more "common" version of that name.
Here is the example from Application Insights JS SDK GitHub on how to modify pageView's data before it's sent out. With the slight modification you may use it to change event names based on their appearance:
window.appInsights = appInsights;
...
// Add telemetry initializer
appInsights.queue.push(function () {
appInsights.context.addTelemetryInitializer(function (envelope) {
var telemetryItem = envelope.data.baseData;
// To check the telemetry item’s type:
if (envelope.name === Microsoft.ApplicationInsights.Telemetry.PageView.envelopeType) {
// this statement removes url from all page view documents
telemetryItem.url = "URL CENSORED";
}
// To set custom properties:
telemetryItem.properties = telemetryItem.properties || {};
telemetryItem.properties["globalProperty"] = "boo";
// To set custom metrics:
telemetryItem.measurements = telemetryItem.measurements || {};
telemetryItem.measurements["globalMetric"] = 100;
});
});
// end
...
appInsights.trackPageView();
appInsights.trackEvent(...);
With help of Dmitry Matveev I've came with the following final code:
var appInsights = window.appInsights;
if (appInsights && appInsights.queue) {
function adjustPageName(item) {
var name = item.name.replace("AppName", "");
if (name.indexOf("Order") !== -1)
return "Order";
if (name.indexOf("Product") !== -1)
return "Shop";
// And so on...
return name;
}
// Add telemetry initializer
appInsights.queue.push(function () {
appInsights.context.addTelemetryInitializer(function (envelope) {
var telemetryItem = envelope.data.baseData;
// To check the telemetry item’s type:
if (envelope.name === Microsoft.ApplicationInsights.Telemetry.PageView.envelopeType || envelope.name === Microsoft.ApplicationInsights.Telemetry.PageViewPerformance.envelopeType) {
// Do not track admin pages
if (telemetryItem.name.indexOf("Admin") !== -1)
return false;
telemetryItem.name = adjustPageName(telemetryItem);
}
});
});
}
Why this code is important? Because App Insights use page titles by default as Name for PageView, so you would have hundreds and thousands of different events, like "Order 123132" which would make further analysis (funnel, flows, events) meaningless.
Key highlights:
var name = item.name.replace("AppName", ""); If you put your App/Product name in title, you probably want to remove it from you event name, because it would just repeat itself everywhere.
appInsights && appInsights.queue you should check for appInsights.queue because for some reason it can be not defined and it would cause an error.
if (telemetryItem.name.indexOf("Admin") !== -1) return false; returning false will cause event to be not recorded at all. There certain events/pages you most likely do not want to track, like admin part of website.
There are two types of events which use page title as event name: PageView
and PageViewPerformance. It makes sense to modify both of them.
Here's one work-around, if you're using templates to render your /orders/12345 pages:
appInsights.trackPageView({name: TEMPLATE_NAME });
Another option, perhaps better suited for a SPA with react-router:
const Tracker = () => {
let {pathname} = useLocation();
pathname = pathname.replace(/([/]orders[/])([^/]+), "$1*"); // handle /orders/NN/whatever
pathname = pathname.replace(/([/]foo[/]bar[/])([^/]+)(.*)/, "$1*"); // handle /foo/bar/NN/whatever
useEffect(() => {
appInsights.trackPageView({uri: pathname});
}, [pathname]);
return null;
}
I want to create a card, once clicked on, it rotates 180°.
So I want to create a card component, that has a plane for the back side and a plane for the front side.
Once clicked on the card-entity, it rotates both planes, so that is looks like the card is turning around.
So here's what I got so far:
custom component: Card, gets added X-times.
A cursor detects a mouseEnter, then I want to card to rotate.
On mouseLeave, the card rotates back to the original state.
So, how can I achieve this the best?
Multiple meshes in a custom component or something?
AFRAME.registerComponent('card', {
schema: {
material: {type:'selector'},
hoverMaterial: {type: 'selector'},
card: {type: 'selector'}
},
multiple: true,
element: null,
init: function () {
console.log("Card loaded");
var data = this.data;
this.el.setAttribute('material', 'src', data.material);
this.el.addEventListener('mouseenter', this.onMouseEnter.bind(this));
this.el.addEventListener('mouseleave', this.onMouseLeave.bind(this));
},
onMouseEnter: function() {
this.el.removeEventListener('mouseenter', this.onMouseEnter.bind(this));
this.el.setAttribute('material', 'src', this.data.hoverMaterial);
this.el.setAttribute('rotation', '0 180 0');
},
onMouseLeave: function () {
//this.el.addEventListener('mouseenter', this.onMouseEnter.bind(this));
this.el.setAttribute('material', 'src', this.data.material);
this.el.setAttribute('rotation', '0 0 0');
},
update: function (oldData) {
},
remove: function () {
this.el.removeObject3D('mesh');
}
});
Create a custom component that appends two child entities to itself with the appropriate orientations and materials - for front and back sides.
You could also add a transparent plane mesh to the component, to enable raycasting.
Reference to the HTML node is this.el and you can manipulate it like a regular HTML node.
Code sample:
AFRAME.registerComponent('card', {
// ...
addChildren: function () {
this.frontFace = document.createElement('a-entity');
this.backFace = document.createElement('a-entity');
// ...
this.el.appendChild(this.frontFace);
this.el.appendChild(this.backFace);
}
// ...
For the input use either a cursor component or the raycaster component itself. Note that you have to have a mesh on the component you are trying to use in raycasting.
So I've read a lot on the discussion of Iron Router vs FlowRouter.
I started my project using Iron Router, but since changed my mind and I'm currently migrating to FlowRouter.
Everything was going smoothly until I started migrating the comments section of my app. You see, this section is reused several times on the app, it serves as a comment section for news, posts, photos, videos, etc.
Example using IR's data context:
Router.route('/news/:slug', {
name: 'newsItem',
waitOn: function() { Meteor.subscribe('news.single', this.params.slug) },
data: function() {
return News.findOne({slug: this.params.slug});
}
});
<template name="newsItem">
<p>{{title}}</p>
<p>{{body}}</p>
{{> commentSection}}
</template>
The Comment collection schema has a "type" (to know to what type of "thing" this comment belongs to, news, photos, etc). That type was set on the "form .submit" event of commentSection template. Example:
'submit form': function(e, template) {
e.preventDefault();
var $body = $(e.target).find('[name=body]');
console.log(template.data.type);
var comment = {
type: template.data.type,
parentId: template.data._id,
parentSlug: template.data.slug,
body: $body.val()
};
Meteor.call('insertComment', comment, function(error, commentId) {
if (error){
alert(error.reason);
} else {
$body.val('');
}
});
}
This worked because the template data context contained the News item which in turn has a a type property as well.
How could I achieve something similar to this only using Flow Router without setting data on the template as it is recommended by the official guide?
You'll want to use a template subscription and a {{#with}} helper probably.
Template.newsItem.onCreated( function() {
Template.instance().subscribe('news.single', FlowRouter.current().params.slug);
});
Template.newsItem.helpers({
item() {
let item = News.findOne();
if( item ) {
return item;
}
}
});
<template name="newsItem">
{{#with item}}
<!-- Your existing stuff -->
{{/with}}
</template>
My auto YUI autocomplete zindex is off. How can I force the autocomplete DIV to the top.
Below I am using a standard template for YUI:
YAHOO.util.Event.onDOMReady(function(){
YUI().use("autocomplete", "autocomplete-filters", "autocomplete-highlighters", function (Y) {
var inputNode = Y.one('#name'),
tags = [
'css',
'css3',
'douglas crockford',
'ecmascript',
'html',
'html5',
'java',
'javascript',
'json',
'node.js',
'pie',
'yui'
],
lastValue = '';
inputNode.plug(Y.Plugin.AutoComplete, {
activateFirstItem: true,
minQueryLength: 0,
queryDelay: 0,
source: tags,
resultHighlighter: 'startsWith',
resultFilters: ['startsWith']
});
// When the input node receives focus, send an empty query to display the full
// list of tag suggestions.
inputNode.on('focus', function () {
inputNode.ac.sendRequest('');
});
// When there are new AutoComplete results, check the length of the result
// list. A length of zero means the value isn't in the list, so reset it to
// the last known good value.
inputNode.ac.on('results', function (e) {
if (e.results.length) {
lastValue = inputNode.ac.get('value');
} else {
inputNode.set('value', lastValue);
}
});
// Update the last known good value after a selection is made.
inputNode.ac.after('select', function (e) {
lastValue = e.result.text;
});
});
});
Simply to put the z-index in the css. Setting via JS used to be allowed, but as of YUI 3.4.0 it's a css-only flag (https://github.com/yui/yui3/blob/master/src/autocomplete/HISTORY.md).
The relevant CSS is (adjust your z-index as necessary):
.yui3-aclist { z-index: 100; }
PS., your YAHOO. line is from YUI2, so that is quite peculiar and definitely not a standard template.
By the time your callback in the YUI().use(...) section is called, the dom should be ready. No ondomready required.