I want to add an html "audio off" button to a webpage containing a WebGL Unity game.
I know I can do adding a button to my webgl game, but I want to do with outside html button.
My code:
<img id="btnaudio" src="images/audio-on.png" style="height: 24px;" data-toggle="tooltip" title="Audio On/Off" onclick="switchAudio();" />
<div id="gameContainer" ></div>
function switchAudio() {
document.getElementById("gameContainer").muted = !document.getElementById("gameContainer").muted;
var img = document.getElementById('btnaudio');
if(document.getElementById("gameContainer").muted)
img.src='images/audio-on.png';
else
img.src='images/audio-off.png';
}
But it not work.. audio keep playing (but image swap so, js code is executed).
Thanks
I think one way would probably be to simply turn off the camera's AudioListener component.
You can call methods in your Unity scene from the page's JavaScript e.g. like
c# code
public class MuteController : MonoBehaviour
{
// For storing previous volumes
private readonly Dictionary<AudioListener, float> mutedListeners = new Dictionary<AudioListener, float>();
public void Mute()
{
foreach (var listener in FindObjectsOfType<AudioListener>())
{
mutedListeners.Add(listener, listener.volume);
listener.volume = 0;
}
}
public void Unmute()
{
foreach (var kvp in mutedListeners)
{
kvp.Key.volume = kvp.Value;
}
mutedListeners.Clear();
}
}
Put it on a GameObject in your scene with the name MuteController.
And then call these methods from the JavaScript code like e.g.
unityInstance.SendMessage('MuteController', 'Mute');
and
unityInstance.SendMessage('MuteController', 'Unmute');
Related
Say I have an element in Blazor. I want to set the elements style based on the value of the input field in Blazor. I am aware that you can do this by using if else blocks in the html code however I want to avoid this as it grows the code base extremely quick. A function would be better which evaluates the incoming object and styles it.
for instance if I have the element below which passes a value the value is passed to a function.
<th><InputText placeholder="First Name" id=FirstName #bind-Value="#filterModel.FirstName">First Name</InputText></th>
Function
private async void UpdateVisibleData(object sender, FieldChangedEventArgs e){
Console.WriteLine(e.FieldIdentifier.FieldName + " Change Detected");
var fieldChanged = e.FieldIdentifier.FieldName;
var IsEmptyString = FindEmptyStringInField(filterModel, fieldChanged);
if(IsEmptyString){
element.style.setProperty('--my-variable-name', 'hotpink');
}
}
I havent been able to get the style.setproperty part to work. not sure what other methods there are. Open to JSInterop. preferrable though if I can target the style.setproperty function and get this working.
Revised Answer
Or you can use JSInterop like this:
/wwwroot/js/example.js
export function examplefunction (Id, Color) {
document.getElementById(Id).style.backgroundColor = Color;
}
razor page :
#inject IJSRuntime JS
#code {
private IJSObjectReference? module;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
module = await JS.InvokeAsync<IJSObjectReference>("import", "/js/example.js");
}
}
private async void UpdateVisibleData(object sender, FieldChangedEventArgs e){
var fieldChanged = e.FieldIdentifier.FieldName;
await module.InvokeVoidAsync("examplefunction", fieldChanged, "hotpink");
}
}
Original Answer
I would use ? operator to make simple HTML without additional code
<input type="text" placeholder="First Name" id=FirstName #bind=#textInput style='#(String.IsNullOrEmpty(textInput) ? "background-color: hotpink;" : "")' />
This isn't a exact answer by targeting the id. however I did find a workaround for this which isn't too bad for webassembly by passing a conditional function to the style tag on the element. It doesn't grow the code base as much as using if else blocks and declaring individual strings. although still targeting element id based on fieldeventchanges would be less computationally taxing as the conditional function is checked for every statechange on every field which uses it as opposed to only being called on the element id if the condition is met.
html
<th><InputText style="#StyleForEmptyString(filterModel.FirstName)" placeholder="First Name" id=FirstName #bind-Value="#filterModel.FirstName">First Name</InputText></th>
#code{
private string StyleForEmptyString(string fieldValue)
{
if (fieldValue == ""){
return "outline:none; box-shadow:none;";
}
return "";
}```
I have an issue with [ngClass]. It doesn't add or remove the class if the Circles in the HTML aren't moving. If the circles are moving it works fine.. Does somebody know why? I dont find anything in the internet.
The isSidebarMenuOpen value is coming from a other component. I got every time the right value. This is why I think the issue is with adding the css class to the div.
If the sidebar opens, I just want to move the map to the right side with a css class
<div [ngClass]="{'open': isSidebarMenuOpen}">
....svg code (circles and other stuff)
</div>
Service.ts
isSidebarMenuOpenSubject = new Subject<any>();
isSidebarMenuOpen = this.isSidebarMenuOpenSubject.asObservable();
getIsSidebarMenuOpen(isSidebarMenuOpen: boolean) {
this.isSidebarMenuOpenSubject.next(isSidebarMenuOpen)
}
Sidebar-Component
toggleSidebarMenu() {
this.isSidebarMenuOpen = !this.isSidebarMenuOpen;
this.resizeService.getIsSidebarMenuOpen(this.isSidebarMenuOpen);
}
map-component
isSidebarMenuOpen = false;
ngOnInit(): void {
this.resizeService.isSidebarMenuOpen.subscribe(value =>{
this.isSidebarMenuOpen = value
});
I have a custom video player with JS, html and css. Crux of my issue here is I didn't anticipate scaling this from one video, to two videos and I'm looking to refactor this so I can play multiple videos on one page. I've tried rewriting everything into a forEach and haven't been able to crack it. Really just need someone to nudge me in the right direction here:
Fiddle
My thinking was to simply change const player = document.querySelector('.custom-video-player'); to const players = document.querySelectorAll('.custom-video-player'); and then scope something like:
players.forEach((player) => {
// declare all the consts here... and event listeners
})
However, this approach isn't really working. Ideally I wanted to be lazy and not rewrite each instance of player. At this point I'm pretty stuck...
HTML
<div class="cs__video">
<div class="custom-video-player">
<video class="player__video viewer" src="http://techslides.com/demos/sample-videos/small.mp4"></video>
</div>
<div class="custom-video-player">
<video class="player__video viewer" src="http://techslides.com/demos/sample-videos/small.mp4"></video>
</div>
</div>
JS
/* custom video player javascripts */
// declaring elements
const player = document.querySelector('.custom-video-player');
const video = player.querySelector('.viewer');
/* Build out functions */
function togglePlay() {
console.log('playing');
const method = video.paused ? 'play' : 'pause';
video[method]();
}
/* event listeners */
video.addEventListener('click', togglePlay);
video.addEventListener('play', updateButton);
video.addEventListener('pause', updateButton);
toggle.addEventListener('click', togglePlay);
You may find it easier to manage the multiple players if you create each one from a class that includes all the relevant setup and methods.
Once you create the class for all players it's easy to create as many as you like.
Here's an example that creates an array of two players from an array of video sources (also available as a fiddle).
class Player {
// We call `new Player()` with two arguments, the id
// and the video source
constructor(id, src) {
// We assign both id and src to the class
this.id = id;
this.src = src;
// Then we call two functions, one to generate the
// video HTML, and one to add it to the page
const html = this.generateHTML(id);
this.addHTMLToDOM(html);
}
// We use a template literal to build our HTML
// using the id and src we passed into the class earlier
generateHTML() {
return (
`<div data-player=${this.id}>Player ${this.id}</div>
<video controls width="250">
<source src="${this.src}" type="video/mp4" />
Sorry, your browser doesn't support embedded videos.
</video>`
);
}
// This method simply adds the player HTML
// to the document body
addHTMLToDOM(html) {
document.body.insertAdjacentHTML('beforeend', html);
}
// play and pause are a couple of example methods for
// player control. `return this` allows for the methods
// to be chained (see below)
play() {
console.log(`Playing video ${this.id}`);
return this;
}
pause() {
console.log(`Pausing video ${this.id}`);
return this;
}
}
// An array of video sources
const srcs = [
'http://techslides.com/demos/sample-videos/small.mp4',
'http://techslides.com/demos/sample-videos/small.mp4'
]
// `map` over the srcs array to create an array of new players
const players = srcs.map((src, i) => new Player(++i, src));
// An example to show how we can call the player instance methods
players[0].play().pause();
players[1].play().pause();
I have an Android test app with a webView like so:
<WebView
android:alpha="0"
android:id="#+id/myWebView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
My Main activity loads a page that I have JS content running. JS is enabled using:
webSettings.setJavaScriptEnabled(true);
There is a button on my app that toggles the alpha value from 0 to 1 (show / hide webView).
Is there any "creative" way of detecting the change on the JS side?
Things I tried:
Checking requestAnimationFrame frame rate changes.
Visibility API
Update:
Clarification, I'm looking for a JS Only solution, the actual JS code is an SDK used inside a WebView environment.
I have no control over the Android app, full control over the WebView content.
You could subclass android's WebView, override its setAlpha method and add some logic to let the webpage know the current alpha value.
Here is a simplified example:
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebView;
public class MyWebView extends WebView {
public MyWebView(Context context) {
super(context);
init();
}
public MyWebView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public MyWebView(Context context,
AttributeSet attrs,
int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
#SuppressLint("SetJavaScriptEnabled")
private void init() {
getSettings().setJavaScriptEnabled(true);
}
#Override
public void setAlpha(float alpha) {
super.setAlpha(alpha);
propagateAlphaToWebPage(alpha);
}
private void propagateAlphaToWebPage(float alpha) {
// setWebViewAlpha is a JS function that should be defined in your html's js
String jssnippet = "setWebViewAlpha("+alpha+");";
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
evaluateJavascript(jssnippet, null);
} else {
loadUrl("javascript:"+jssnippet);
}
}
}
Other related questions you may find helpful:
Android WebView - detect whether a JS function is defined
Android Calling JavaScript functions in WebView
Declaring a custom android UI element using XML
Below is complete code which will tell to JavaScript if WebView is visible or not.
Steps:
Create a layout containing a ToggleButton and WebView.
Load your html using htmlWebView.loadUrl("file:///android_asset/index.html"); and related code
Create a function onToggleButtonPressed() which will be called onClick of toggle button
In onToggleButtonPressed() show/hide WebView and at the same time pass the status to JavaScript using htmlWebView.evaluateJavascript() method. Pass visibilityStatusFromAndroid() to JavaScript
Get the status in JavaScript from visibilityStatusFromAndroid() function
Android Layout xml code:
<ToggleButton
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:background="#color/red"
android:onClick="onToggleButtonPressed"
android:text="Button1"
android:textOn="Show"
android:textOff="Hide"
tools:ignore="MissingConstraints"
tools:layout_editor_absoluteX="148dp"
tools:layout_editor_absoluteY="0dp" />
<WebView
android:id="#+id/webView"
android:layout_width="368dp"
android:layout_height="447dp"
android:layout_alignParentStart="true"
android:layout_below="#+id/button"
tools:ignore="MissingConstraints"
tools:layout_editor_absoluteX="8dp"
tools:layout_editor_absoluteY="56dp"
android:layout_alignParentLeft="true" />
Java Code:
WebView htmlWebView; // put it outside onCreate() to make it accessible in onToggleButtonPressed()
htmlWebView = (WebView)findViewById(R.id.webView);
htmlWebView.setWebViewClient(new WebViewClient());
htmlWebView.loadUrl("file:///android_asset/index.html");
WebSettings webSetting = htmlWebView.getSettings();
webSetting.setJavaScriptEnabled(true);
public void onToggleButtonPressed(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
boolean on = ((ToggleButton) view).isChecked();
String visibility;
if (on) {
htmlWebView.setVisibility(View.GONE);
visibility = "NOT VISIBLE";
htmlWebView.evaluateJavascript("javascript: " +
"visibilityStatusFromAndroid(\"" + visibility + "\")", null);
} else {
htmlWebView.setVisibility(View.VISIBLE);
visibility = "VISIBLE NOW";
htmlWebView.evaluateJavascript("javascript: " +
"visibilityStatusFromAndroid(\"" + visibility + "\")", null);
}
}
}
JavaScript file:
function visibilityStatusFromAndroid(message) {
if (message === "NOT VISIBLE") {
console.log("webview is not visible");
// do your stuff here
} else if (message === "VISIBLE NOW") {
console.log("webview is visible now");
// do your stuff here
}
}
That's it. You are DONE!
Here is Video of working code.
Update:
There is one option that you can use to detect visibility only using JavaScript is document.hasFocus() but this will not work if there is any EditText on the screen and user will focus that EditText.
Just in case below is the code you can use in for document.hasFocus():
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>TEST</title>
<style>
#message { font-weight: bold; }
</style>
<script>
setInterval( checkPageFocus, 200 ); // you can change interval time to check more frequently
function checkPageFocus() {
var info = document.getElementById("message");
if ( document.hasFocus() ) {
info.innerHTML = "The document is VISIBLE.";
} else {
info.innerHTML = "The document is INVISIBLE.";
}
}
</script>
</head>
<body>
<h1>JavaScript hasFocus example</h1>
<div id="message">Waiting for user action</div>
</body>
</html>
There is (currently) no JS-only solution.
The content of the WebView isn't supposed to be able to get information about its environment.
The only exception is information which was volunteered by the environment itself.
This would contradict basic sandboxing and could lead to security issues.
As there is no standard API that would give you the information you want you would have to change the environment yourself to expose it to the WebView.
If you can't change the environment what you are requesting is (theoretically) impossible with the current browsers/android.
#Hack To find an actual loophole we could exploit to get the alpha value we would need more detailed information about the JS SDK, the environment and how much influence on the android / webview side of the app you have. And even then after investing much effort the result would most likely be the same.
Angular2/Typescript - Parent/Child Directive(?)
I'm new to, and very much still learning, Angular2/Typescript/javascript. As a result, I'm not entirely sure how to title my question. The basis of my app is a card game. The premise (relative to my struggle) is that the game has 2 players and each player has a hand of 5 cards. I have API calls to build/return the hand of cards.
In my app.component template, I have 2 div blocks; one for each players' hand of cards. Currently, I have it working by building two distinct arrays of cards (named p1cards and p2cards). Here is the relative code for that:
<div class="player1Cards" id="player1Cards">
<ul class="list-group">
<div draggable *ngFor="let card of p1cards" [dragData]="card"
class="list-group-item">
<img src="{{cardBluePath + card.fileName}}">
</div>
</ul>
</div>
<div class="player2Cards" id="player2Cards">
<ul class="list-group">
<div draggable *ngFor="let card of p2cards" [dragData]="card"
class="list-group-item">
<img src="{{cardBluePath + card.fileName}}">
</div>
</ul>
</div>
And here is the actual export class of the entire AppComponent:
#Injectable()
export class AppComponent implements OnInit
{
#ViewChild(ModalComponent) errorMsg: ModalComponent;
errorMessage: string;
gameBoard: GameBoard[];
name: {};
mode = 'Observable';
//we need a gameboard (maybe not)
//we need an array of players
player:Player;
players:Player[] = [];
p1cards:Card[] = [];
p2cards:Card[] = [];
droppedItems = [];
//This tells us where the card images can be found
cardBluePath = "/assets/deck/Blue/";
cardRedPath = "/assets/deck/Red/";
//The boardService will handle our API calls
boardService;
//Initialize the API service
constructor(boardService:BoardService) {
this.boardService = boardService;
}
//On load...
ngOnInit()
{
//Create the game
this.boardService.createGame()
.subscribe(
error => this.errorMessage = <any>error);
//Create the players
this.createPlayer(0);
this.createPlayer(1);
}
createPlayer(player: number)
{
var playerName;
if (player == 0) {playerName = "Player1"} else {playerName = "Player2"};
//We'll make a call to the API to build the hand of cards
this.boardService.buildHand(player)
.subscribe(
cardList =>
{
var cardData = [];
cardData = JSON.parse(cardList.toString());
var i, itemLength, card
itemLength = cardData.length;
for(i=0;i<itemLength;i++)
{
let card = new Card();
Object.assign(card,
{
"cardNum":i,
"id": cardData[i].id,
"displayName": cardData[i].displayName,
"fileName": cardData[i].fileName,
"left": cardData[i].left,
"top": cardData[i].top,
"right": cardData[i].right,
"bottom": cardData[i].bottom,
"level": cardData[i].level,
"native": cardData[i].native
});
if (player == 0) {this.p1cards.push(card)} else {this.p2cards.push(card)};
////this.cards.push(card);
}
//Now we will create the player and feed it the hand
this.player = new Player(playerName);
if (player ==0) {this.player.cardHand = this.p1cards} else {this.player.cardHand = this.p2cards};
this.players.push(this.player);
}
);
}
//When a card is dropped...
onItemDrop(e: any, slot: any)
{
e.dragData.slot = slot;
//Update the object
this.boardService.playCard(slot, e.dragData.card)
.subscribe(result => {
//If the slot is open and the card is played, physically move the item
if (result == "true" )
{
this.droppedItems.push(e.dragData);
this.removeItem(e.dragData, this.p1cards);
}
else{
window.alert("Slot already occupied.");
//this.modalWindow.show()
//this.errorMsg.showErrorMessage("Slot already occupied.");
//this.errorMsg.show();
}
});
}
//Remove the card from the hand
removeItem(item: any, list: Array<any>)
{
let index = list.map((e) => {
return e.cardNum
}).indexOf(item.cardNum);
list.splice(index, 1);
}
}
The createPlayer function is really where the question begins. Currently, it will make the API call and parse the JSON back into an array of cards. Right now, the array of cards lives locally in the AppComponent (as p1cards or p2cards).
What I want to do instead is create a player objects (component) for each player, assign their respective hand of cards, and then put those players in an array. I had that part working (pieces of the code still exist above, but not all of it), but I hit a wall in my *ngFor to display the cards. In pseudocode, I understood what I needed to do, but in practice I couldn't figure it out.
I knew that div class player1Cards needed to be something like "let player of Players where name = player1", and then I needed to iterate over the player.cardHand[] array to display each of the cards. I tried quite a few things, but nothing worked.
So then, after a few hours of Google searching, I came to the conclusion that I needed a child view for the player to handle it. I currently have the following for that:
My player.html is:
<div draggable *ngFor="let card of cardHand" [dragData]="card" class="list-group-item">
<img src="{{cardBluePath + card.fileName}}">
</div>
And my player.ts is:
import { Component, Input, OnInit } from '#angular/core';
import { Card } from './card';
#Component({
selector: 'player',
templateUrl: './player.html',
})
export class Player implements OnInit
{
public cardHand: Card[];
cardBluePath = "/assets/deck/Blue/";
constructor
(
public name: string
)
{}
ngOnInit()
{
}
}
Then in my AppComponent template, I added the block (and Imported the player.ts)
I get an error message on App.Component "inline template:69:16 caused by: No provider for String!". Of all the Google research I performed and all of the changes I tried (ViewChild, Input/Output, Reference), I could not get it to work. I don't recall exactly what I did, but at one point I was able to eliminate the error, but the card array was not getting passed to the player (I wish I had committed or stashed that code).
In my mind, I understand the task at hand, I just can't make it happen. I know I need to create the Player object and feed it the respective cardHand in order for the player html to be able to parse it. I can do that fine in AppComponent, but once I try to do it as a parent/child, I get stuck.
Can someone help get me going in the right direction?
I know I need to create the Player object and feed it the respective
cardHand in order for the player html to be able to parse it. I can do
that fine in AppComponent, but once I try to do it as a parent/child,
I get stuck.
I agree that it makes sense to create an array of player objects, each with an array of cards in their hand. Something like this:
let player1 = {name:'Player 1',hand:[]}
let player2 = {name:'Player 2',hand:[]}
this.players = [player1, player2]
player1.hand.push(this.dealCard())
...
player2.hand.push(this.dealCard())
...
You can then create a player component to show the players (and even a card component to show their hand). In your root template you'll loop through the players, creating your player component and passing in the player data, including their hands.
<player-component *ngFor="let player of players" [player]="player"></player-component>
Make sure your player component has an input to receive the player data:
export class PlayerComponent implements OnInit {
#Input() player: Player;
constructor() { }
ngOnInit() { }
}
Then in the <player-component> template loop through the player's hand and render the cards:
<p>I am {{player.name}}. My hand is:</p>
<ul>
<li *ngFor="let card of player.hand">{{card}}</li>
</ul>
Here is a plunker showing a working demo that's a simplified version of this setup:
https://plnkr.co/edit/5Hz8P7poCb9Ju5IR6MWs?p=preview
You should be able to configure it to the specific setup of your game. Good luck!
in your player component, if you want to access another component:
1. that component needs a import statement on top
2. within the #component section, you need to include it in Providers
3. also include it in the constructor
For more information, visit here: https://angular.io/guide/dependency-injection