How do I set the volume of a Youtube video? - google-chrome-extension

I'm creating an extension that mutes the autoplay video when you navigate to a Youtube channel. It's able to execute content.js when navigating to a Youtube channel and select the video, but how can I set the video's volume? Setting video.volume = 0 does not work like I thought it would.
Background script:
'use strict';
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if (changeInfo.status == 'complete') {
const site = tab.url.split('/');
let domain = site[2];
let page = site[3];
if (page === 'channel' || page === 'user') {
chrome.tabs.sendMessage(tab.id, {'message': 'loaded_channel_page'});
}
}
});
Content script:
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.message === 'loaded_channel_page' || document.readyState != 'loading') {
let newVolume = 0;
document.addEventListener('yt-navigate-finish', () => {
setTimeout(mute, 2000);
});
function mute() {
let video = document.querySelector('.html5-main-video');
video.volume = newVolume;
}
}
})

Youtube doesn't monitor direct changes to the DOM video element's properties, they use their own wrapper on a #movie_player element that exposes the volume control function in page context which we can't access directly from the content script so we need to run it in a <script> element:
// direct call
inPage(setVolume, 0);
// delayed call
setTimeout(inPage, 2000, setVolume, 0);
// range is 0 - 100
function setVolume(vol) {
document.getElementById('movie_player').setVolume(vol);
}
function inPage(fn, ...args) {
const script = document.createElement('script');
script.textContent = `(${fn})(${JSON.stringify(args).slice(1, -1)})`;
document.documentElement.appendChild(script);
script.remove();
}

Related

Reusing a function for buttons that work independently of each other?

I'm trying to create something with P5.js that resembles an audio looper—that is, you record a snippet of audio, it plays back to you as a loop, and then you can record other snippets of audio to play together to create a song.
I figured out how to record audio, play it back as a loop, and stop the loop, but I'm not sure the best way to repeat that function so that it can be used for buttons that function independently of each other and record separate audio files, as I would like to record multiple loops.
I'm still pretty new to P5.js, so there's probably a simple way to do this—any ideas help! In general, if you have any ideas on how to achieve this project as a whole (the audio looper) I'd love to hear them.
This is my code:
let mic, recorder, soundFile, button;
let state = 0;
function setup() {
createCanvas(windowWidth, windowHeight);
background(200);
mic = new p5.AudioIn();
mic.start();
recorder = new p5.SoundRecorder();
recorder.setInput(mic);
soundFile = new p5.SoundFile();
button = createButton("record");
button.size(200, 100);
button.style("font-family", "Bodoni");
button.style("font-size", "48px");
button.position(10, 10, 10);
button.mouseClicked(loopRecord);
}
// this is the looper
function loopRecord() {
if (state === 0 && mic.enabled) {
recorder.record(soundFile);
background(255, 0, 0);
state++;
} else if (state === 1) {
recorder.stop();
background(0, 255, 0);
state++;
} else if (state === 2) {
soundFile.loop();
state++;
} else if (state === 3) {
soundFile.stop();
state++;
} else if (state === 4) {
state === 0;
}
}
You should somehow structure your data so that each button has its own state (and other parameters that should not be global, but instead unique to each loop).
Here an example using an array of track objects:
https://editor.p5js.org/RemyDekor/sketches/gM75vBYmk
I'll copy the code here as well
let mic
// this array will be filled afterwards
let tracksArray = [];
function setup() {
createCanvas(400, 400);
background(200);
// we need only one mic (it is still global)
mic = new p5.AudioIn();
mic.start();
// we call a function that creates a custom "track" object and puts it in the tracksArray (which you could use for some other things, but is currently un-used)
createTrack();
// we create a button that call the same function when we click on it
const addTrackButton = createButton("add track");
addTrackButton.mouseClicked(createTrack)
addTrackButton.position(10, 10)
}
// We now use names (strings) instead of abstract numbers to set the state of the track
function handleTrackButtonClick(trackObject) {
if (trackObject.state === "idle" && mic.enabled) {
trackObject.recorder.record(trackObject.soundFile);
background(255, 0, 0);
trackObject.state = "recording";
console.dir(trackObject.button.elt.innerText = "[recording..]");
} else if (trackObject.state === "recording") {
trackObject.recorder.stop();
background(0, 255, 0);
trackObject.state = "stopped";
console.dir(trackObject.button.elt.innerText = "play");
} else if (trackObject.state === "stopped") {
trackObject.soundFile.loop();
trackObject.state = "playing";
console.dir(trackObject.button.elt.innerText = "[playing..] stop")
} else if (trackObject.state === "playing") {
trackObject.soundFile.stop();
trackObject.state = "stopped";
console.dir(trackObject.button.elt.innerText = "[stopped..] play")
}
}
function createTrack() {
const newButton = createButton("record");
newButton.style("font-family", "Bodoni");
newButton.style("font-size", "48px");
// we do not position this button anymore, and use the "flow" of the html document
newButton.style("display", "block");
// this is the "track" object we create, and we attach the previously global parameters/states on it
const newTrackObject = {
button: newButton,
state: "idle",
recorder: new p5.SoundRecorder(),
soundFile: new p5.SoundFile()
};
newButton.mouseClicked(function() {
handleTrackButtonClick(newTrackObject)
});
newTrackObject.recorder.setInput(mic);
tracksArray.push(newTrackObject);
}

Cast Application Framework receiver app not working on external cast device 2nd Geneation

I'm trying to develop CAF v3 receiver app and it's working with in-built and setup boxes cast devices but not working on external cast devices.
External cast devices NC2-A65 throws PIPELINE_INITIALIZATION_ERROR or VIDEO_ERROR with shaka error code 3016
After debugging, observation is drm License Url doesn't get called when useLegacyDashSupport is true
Any help is appreciated
Here is the code,
<script>
const context = cast.framework.CastReceiverContext.getInstance();
context.setLoggerLevel(cast.framework.LoggerLevel.DEBUG);
const options = new cast.framework.CastReceiverOptions();
const castDebugLogger = cast.debug.CastDebugLogger.getInstance();
const playerManager = context.getPlayerManager();
const playbackConfig = (Object.assign(new cast.framework.PlaybackConfig(), playerManager.getPlaybackConfig()));
options.maxInactivity = 3600;
options.supportedCommands = cast.framework.messages.Command.ALL_BASIC_MEDIA;
castDebugLogger.setEnabled(true);
// Show debug overlay
castDebugLogger.showDebugLogs(true);
let useLegacyDashSupport = false;
if (context.canDisplayType('video/mp4; codecs="avc1.640028"; width=3840; height=2160')) {
// The device and display can both do 4k. Assume a 4k limit.
castDebugLogger.info("Hardware Resolution: ", '3840x2160');;
options.touchScreenOptimizedApp = true;
} else {
// Chromecast has always been able to do 1080p. Assume a 1080p limit.
castDebugLogger.info("Hardware Resolution: ", '1920x1080');
useLegacyDashSupport = true;
}
options.useLegacyDashSupport = useLegacyDashSupport;
context.loadPlayerLibraries(useLegacyDashSupport);
context.addEventListener(cast.framework.system.EventType.ERROR, event => {
castDebugLogger.info('Context Error - ', JSON.stringify(event));
});
playerManager.addEventListener(cast.framework.events.EventType.ERROR, event => {
castDebugLogger.info('Error - ', "ERROR event: " + JSON.stringify(event));
});
playerManager.addEventListener(cast.framework.events.EventType.MEDIA_STATUS, (event) => {
castDebugLogger.info('Player State - ', event.mediaStatus.playerState);
});
// Intercept the LOAD request to be able to read in a contentId and get data.
playerManager.setMessageInterceptor(cast.framework.messages.MessageType.LOAD, loadRequestData => {
castDebugLogger.info('LoadRequest Data - ', JSON.stringify(loadRequestData));
const error = new cast.framework.messages.ErrorData(cast.framework.messages.ErrorType.LOAD_CANCELLED);
if (!loadRequestData.media) {
error.reason = cast.framework.messages.ErrorReason.INVALID_PARAM;
return error;
}
if (!loadRequestData.media.contentId) {
error.reason = cast.framework.messages.ErrorReason.INVALID_PARAM;
return error;
}
loadRequestData.autoplay = true;
let url = loadRequestData.media.contentId;
castDebugLogger.info('Content Id - ', url);
const ext = url.substring(url.lastIndexOf('.'), url.length);
loadRequestData.media.contentType = 'video/mp4';
if (ext.includes('mpd')) {
loadRequestData.media.contentType = 'application/dash+xml';
} else if (ext.includes('m3u8')) {
loadRequestData.media.contentType = 'application/vnd.apple.mpegurl';
// TODO: Create option to set hlsSegmentFormat option.
loadRequestData.media.hlsSegmentFormat = cast.framework.messages.HlsSegmentFormat.TS;
} else if (ext.includes('ism')) {
loadRequestData.media.contentType = 'application/vnd.ms-sstr+xml';
}
if (loadRequestData.media.customData && loadRequestData.media.customData.drm) {
playerManager.setMediaPlaybackInfoHandler((loadRequest, playbackConfigData) => {
playbackConfigData.licenseUrl = loadRequest.media.customData.drm.widevine.url;
playbackConfigData.protectionSystem = cast.framework.ContentProtection.WIDEVINE;
castDebugLogger.info('PlaybackConfig Data - ', JSON.stringify(playbackConfigData));
return playbackConfigData;
});
}
return loadRequestData;
});
options.playbackConfig = playbackConfig;
context.start(options);
</script>

Chrome Extension - Background Script notified whenever another page updates

I'm making a Chrome Extension that gets the DOM of a closed tab and updates the popup.html. So far so good, I can do that through the background script using XMLHttpRequest.
However, I would like my popup to be updated if the closed page is updated. I was thinking of running a timer in the background script to check every 10 sends or so, but I was wondering if XMLHttpRequest has a way of knowing when its page updates? Or even if the timer would work, I couldn't get it working
I've added the relevant files below. Any help is appreciated
popup.html
<body>
<h1>Agile Board Viewer</h1>
<div class="wrapper">
<button id="mainButton">Click me</button>
<p id="testingDisplay">test</p>
</div>
</body>
popup.js
document.addEventListener('DOMContentLoaded', function () {
document.getElementById("mainButton").addEventListener('click', function () {
chrome.runtime.sendMessage({
method : 'POST',
action : 'xhttp',
url : '//My url//',
data : 'q=something'
}, function (responseText) {
document.getElementById("testingDisplay").innerHTML = responseText;
});
});
});
background.js
I've deleted some lines that are pointless (I think) to avoid clutter, just error handlers and what not, also got rid of my attempt at a timer. Basically, what it does is takes a string from the DOM and sends it to the popup. I would like that popup to update whenever the string does.
var testingString = "Testing (";
chrome.runtime.onMessage.addListener(function (request, sender, callback) {
if (request.action == "xhttp") {
`var xhttp = new XMLHttpRequest();
xhttp.onload = function () {
var testingValue = xhttp.responseText.substring(xhttp.responseText.indexOf(testingString), xhttp.responseText.indexOf(testingString) + 16);
callback(testingValue);
//callback(xhttp.responseText);
};
}
});
Sorry if the formatting is a mess, I'm not too well versed on this
Just to follow up, I've solved my issue by using a timer that checks the closed tab every couple of seconds. If the label that lists the number of items in my Testing column is different, I get my notification. Still learning XHR so I'm hoping I can improve on this again but for now, I'm happy enough. Only works for 20 seconds in my example, as I don't want an infinite timer. Will put in an off switch later
var testingString = "Testing (";
var testBuffer = "";
var i = 0;
chrome.runtime.onMessage.addListener(function (request, sender, callback) {
if (request.action == "xhttp") {
var xhttp = new XMLHttpRequest();
var method = request.method ? request.method.toUpperCase() : 'GET';
var testingValue = xhttp.responseText.substring(xhttp.responseText.indexOf(testingString), xhttp.responseText.indexOf(testingString) + 16);
function startTimer() {
window.setTimeout(function () {
if (i < 20) {
xhttp.open(method, request.url, true);
xhttp.onload = function () {
testBuffer = testingValue;
testingValue = xhttp.responseText.substring(xhttp.responseText.indexOf(testingString), xhttp.responseText.indexOf(testingString) + 16);
if (testBuffer != testingValue) {
notification = new Notification('New Item in Testing Column', {
body : "You have a new item in Testing",
});
console.log(testingValue);
}
};
xhttp.onreadystatechange = function () {
if (xhttp.readyState == 4 && xhttp.status == 200) {
callback(testingValue);
}
};
xhttp.onerror = function () {
alert("error");
callback();
};
if (method == 'POST') {
xhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
}
xhttp.send(request.data);
console.log("count");
i++;
startTimer();
}
}, 5000);
}
startTimer();
return true;
}
});

Identify true activeElement in a frame hierarchy

I'm looking for a way to get the focused input of any page with the goal to update it's value.
I have a content script that receives the value and checks if activeElement is an <input>.
The challenge is when the main document's activeElement is an iframe. I can set all_frames: true in manifest, which will find any active <input>, but I only want to set the value of the activeElement of the active iframe.
Currently I'm solving this by letting the child content script(s) blur() all activeElements except the current one (attaching a handler to focusin), but a solution that does not modify the document state would be better imho.
Is it possible to send a message only to the main document and if this one's activeElement is an iframe, get that frameId (and try again)?
I don't control the web pages.
Inject a new content script that checks the full hierarchy of frames, including cross-domain frames.
main content script asks the background/event page if needed:
if (document.activeElement.matches('input') && window == parent) {
console.log('Input', document.activeElement);
document.activeElement.value += ' gotcha!';
} else {
chrome.runtime.sendMessage({action: 'modifyInput'});
}
background/event page script executes the additional content script in all frames:
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse)) {
if (msg.action == 'modifyInput') {
chrome.tabs.executeScript(sender.tab && sender.tab.id, {
file: 'modify-input.js',
allFrames: true,
matchAboutBlank: true,
runAt: 'document_end',
});
}
});
modify-input.js, IIFE is used to makes sure garbage collection removes the injected stuff.
;(function() {
if (isInputActive()) {
if (window == parent) {
foundTheInput();
} else {
askParent();
}
} else if (isFrameActive()) {
window.addEventListener('message', function onMessage(e) {
if (!e.data || e.data.id != chrome.runtime.id)
return;
switch (e.data.action) {
// request from a child frame
case 'checkme':
if (window == parent) {
confirmChild(e.source);
window.removeEventListener('message', onMessage);
} else {
askParent();
}
break;
// response from a parent
case 'confirmed':
window.removeEventListener('message', onMessage);
if (isInputActive()) {
foundTheInput();
} else if (isFrameActive()) {
confirmChild(e.source);
}
break;
}
});
}
function isInputActive() {
return document.activeElement.matches('input');
}
function isFrameActive() {
return document.activeElement.matches('frame, iframe');
}
function askParent() {
parent.postMessage({id: chrome.runtime.id, action: 'checkme'}, '*');
}
function confirmChild(childWindow) {
console.log('Frame', document.activeElement);
childWindow.postMessage({id: chrome.runtime.id, action: 'confirmed': true}, '*');
}
function foundTheInput() {
console.log('Input', document.activeElement);
document.activeElement.value += ' gotcha!';
}
})();
I didn't test this, but did use and saw similar code previously.

How do you implement a stream that properly handles backpressure in node.js?

I can't for the life of me figure out how to implement a stream that properly handles backpressure. Should you never use pause and resume?
I have this implementation I'm trying to get to work correctly:
var StreamPeeker = exports.StreamPeeker = function(myStream, callback) {
stream.Readable.call(this, {highWaterMark: highWaterMark})
this.stream = myStream
myStream.on('readable', function() {
var data = myStream.read(5000)
//process.stdout.write("Eff: "+data)
if(data !== null) {
if(!this.push(data)) {
process.stdout.write("Pause")
this.pause()
}
callback(data)
}
}.bind(this))
myStream.on('end', function() {
this.push(null)
}.bind(this))
}
util.inherits(StreamPeeker, stream.Readable)
StreamPeeker.prototype._read = function() {
process.stdout.write("resume")
//this.resume() // putting this in for some reason causes the stream to not output???
}
It correctly sends output, but doesn't correctly produce backpressure. How can I change it to properly support backpressure?
Ok I finally figured it out after lots of trial and error. A couple guidelines:
Never ever use pause or resume (otherwise it'll go into legacy "flowing" mode)
Never add a "data" event listener (otherwise it'll go into legacy "flowing" mode)
Its the implementor's responsibility to keep track of when the source is readable
Its the implementor's responsibility to keep track of when the destination wants more data
The implementation should not read any data until the _read method is called
The argument to read tells the source to give it that many bytes, it probably best to pass the argument passed to this._read into the source's read method. This way you should be able to configure how much to read at a time at the destination, and the rest of the stream chain should be automatic.
So this is what I changed it to:
Update: I created a Readable that is much easier to implement with proper back-pressure, and should have just as much flexibility as node's native streams.
var Readable = stream.Readable
var util = require('util')
// an easier Readable stream interface to implement
// requires that subclasses:
// implement a _readSource function that
// * gets the same parameter as Readable._read (size)
// * should return either data to write, or null if the source doesn't have more data yet
// call 'sourceHasData(hasData)' when the source starts or stops having data available
// calls 'end()' when the source is out of data (forever)
var Stream666 = {}
Stream666.Readable = function() {
stream.Readable.apply(this, arguments)
if(this._readSource === undefined) {
throw new Error("You must define a _readSource function for an object implementing Stream666")
}
this._sourceHasData = false
this._destinationWantsData = false
this._size = undefined // can be set by _read
}
util.inherits(Stream666.Readable, stream.Readable)
Stream666.Readable.prototype._read = function(size) {
this._destinationWantsData = true
if(this._sourceHasData) {
pushSourceData(this, size)
} else {
this._size = size
}
}
Stream666.Readable.prototype.sourceHasData = function(_sourceHasData) {
this._sourceHasData = _sourceHasData
if(_sourceHasData && this._destinationWantsData) {
pushSourceData(this, this._size)
}
}
Stream666.Readable.prototype.end = function() {
this.push(null)
}
function pushSourceData(stream666Readable, size) {
var data = stream666Readable._readSource(size)
if(data !== null) {
if(!stream666Readable.push(data)) {
stream666Readable._destinationWantsData = false
}
} else {
stream666Readable._sourceHasData = false
}
}
// creates a stream that can view all the data in a stream and passes the data through
// correctly supports backpressure
// parameters:
// stream - the stream to peek at
// callback - called when there's data sent from the passed stream
var StreamPeeker = function(myStream, callback) {
Stream666.Readable.call(this)
this.stream = myStream
this.callback = callback
myStream.on('readable', function() {
this.sourceHasData(true)
}.bind(this))
myStream.on('end', function() {
this.end()
}.bind(this))
}
util.inherits(StreamPeeker, Stream666.Readable)
StreamPeeker.prototype._readSource = function(size) {
var data = this.stream.read(size)
if(data !== null) {
this.callback(data)
return data
} else {
this.sourceHasData(false)
return null
}
}
Old Answer:
// creates a stream that can view all the data in a stream and passes the data through
// correctly supports backpressure
// parameters:
// stream - the stream to peek at
// callback - called when there's data sent from the passed stream
var StreamPeeker = exports.StreamPeeker = function(myStream, callback) {
stream.Readable.call(this)
this.stream = myStream
this.callback = callback
this.reading = false
this.sourceIsReadable = false
myStream.on('readable', function() {
this.sourceIsReadable = true
this._readMoreData()
}.bind(this))
myStream.on('end', function() {
this.push(null)
}.bind(this))
}
util.inherits(StreamPeeker, stream.Readable)
StreamPeeker.prototype._read = function() {
this.reading = true
if(this.sourceIsReadable) {
this._readMoreData()
}
}
StreamPeeker.prototype._readMoreData = function() {
if(!this.reading) return;
var data = this.stream.read()
if(data !== null) {
if(!this.push(data)) {
this.reading = false
}
this.callback(data)
}
}

Resources