Zaczynam drugie podejście do Rainbow Star Radio – tzw. radio z wizją. W ubiegłym roku, przez sześć miesięcy prowadziłem testy nadawania w technologii Nullsoft Shoutcast, ale – jakkolwiek udało się super – nie o to mi chodziło. Plan bowiem był taki, żeby transmitować na żywo na YouTube, zapełnić stream produkcją własną i licencjonowaną. Testy, choć wciąż na razie muzyczne dzieją się właśnie na kanale Rainbow Star, na YouTube.
Czymże by była transmisja takiego programu, gdyby nie było w niej… pogody? 😀 Szczęśliwie mam szerokie kompetencje, w streamingu jestem dobry, umiem sam bez grantów zrobić wiele rzeczy, więc skutek jest – i owszem.
Poniższy kod odpytuje serwis OpenWeather na okoliczność temperatury i warunków atmo w różnych miejscowościach. Koncept został opracowany po to, żeby umieścić go jako aktywowaną co jakiś czas scenę w OBS.
Enjoy!
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dane Pogodowe</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@300;500;700;800;900&display=swap');
body {
display: flex;
justify-content: center; /* Wyśrodkowanie w poziomie */
align-items: center; /* Wyśrodkowanie w pionie */
min-height: 100vh; /* Pełna wysokość widoku */
margin: 0; /* Usunięcie domyślnego marginesu */
background-color: #1c77c3;
background-image: radial-gradient(circle, #F9A72B 0%, #FA9026 70%, #FB6C1F 100%);
}
#weatherData {
transition: opacity 1s ease-in-out;
opacity: 0; /* zacznij jako niewidoczny */
text-align:center;
z-index: 100;
}
.fade-in {
animation: fadeIn 1.5s forwards;
}
.fade-out {
animation: fadeOut 1.5s forwards;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
h1 {font-family: 'Roboto Condensed'; font-weight: 700; font-size: 100px; color: #fff; text-transform:uppercase; margin-top: 0;}
h2 {font-family: 'Roboto Condensed'; font-weight: 400; font-size: 60px; color: #fff; margin-top:-80px;}
/* Styl głównego obrazu */
.mainicon {
width: 300px;
height: auto;
filter: invert(100%);
}
/* Styl dodatkowych ikon */
.iconadd {
padding: 10px;
width: 100px; /* !important powinno być używane jako ostateczność */
height: 100px; /* !important powinno być używane jako ostateczność */
filter: invert(100%);
}
/* Styl tabeli prognozy */
.forecastimg {
width: 100%;
max-width: 900px;
table-layout: fixed;
border-collapse: separate;
border-spacing: 3px;
}
/* Styl wspólny dla czasu, prognozy i temperatury */
.czas, .prognoza, .temperatura {
font-family: 'Roboto Condensed', sans-serif; /* Dodane sans-serif jako fallback */
font-weight: 400;
font-size: 24px;
color: #000;
text-align: center;
padding: 10px;
background: transparent;
border: 1px solid #fff; /* Ustaw na 1px jak w poprzednim stylu, albo 2px jak w twoim ostatnim */
}
/* Styl specyficzny dla czasu */
.czas {
border-top-left-radius: 6px;
border-top-right-radius: 6px;
background: #fff;
}
/* Styl specyficzny dla temperatury */
.temperatura {
border-bottom-right-radius: 6px;
border-bottom-left-radius: 6px;
background: #fff;
}
/* animacja tła */
#Clouds {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
height: 30%;
overflow: hidden;
animation: FadeIn 3.1s ease-out;
user-select: none;
}
@keyframes FadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.Cloud {
position: absolute;
width: 100%;
background-repeat: no-repeat;
background-size: auto 100%;
height: 70px;
animation-duration: 120s;
animation-iteration-count: infinite;
animation-fill-mode: forwards;
animation-timing-function: linear;
animation-name: Float, FadeFloat;
z-index: 2;
}
.Cloud.Foreground {
height: 10%;
min-height: 20px;
z-index: 4;
}
.Cloud.Background {
height: 9.09090909%;
min-height: 8px;
animation-duration: 210s;
}
@keyframes Float {
from {
transform: translateX(100%) translateZ(0);
}
to {
transform: translateX(-15%) translateZ(0);
}
}
/*
@keyframes Float {
from { transform: translateX(100%) translateY(-100%) translateZ(0); }
50% { transform: translateX(55%) translateY(0) translateZ(0); }
to { transform: translateX(-5%) translateY(-100%) translateZ(0); }
}
*/
@keyframes FadeFloat {
0%,
100% {
opacity: 0;
}
5%,
90% {
opacity: 1;
}
}
.Cloud:nth-child(10) {
animation-delay: -184.61538462s;
top: 60%;
}
.Cloud.Foreground:nth-child(10) {
animation-duration: 80s;
height: 35%;
}
.Cloud.Background:nth-child(10) {
animation-duration: 110s;
height: -3.40909091%;
}
.Cloud:nth-child(9) {
animation-delay: -166.15384615s;
top: 54%;
}
.Cloud.Foreground:nth-child(9) {
animation-duration: 84s;
height: 32.5%;
}
.Cloud.Background:nth-child(9) {
animation-duration: 114s;
height: -2.15909091%;
}
.Cloud:nth-child(8) {
animation-delay: -147.69230769s;
top: 48%;
}
.Cloud.Foreground:nth-child(8) {
animation-duration: 88s;
height: 30%;
}
.Cloud.Background:nth-child(8) {
animation-duration: 118s;
height: -0.90909091%;
}
.Cloud:nth-child(7) {
animation-delay: -129.23076923s;
top: 42%;
}
.Cloud.Foreground:nth-child(7) {
animation-duration: 92s;
height: 27.5%;
}
.Cloud.Background:nth-child(7) {
animation-duration: 122s;
height: 0.34090909%;
}
.Cloud:nth-child(6) {
animation-delay: -110.76923077s;
top: 36%;
}
.Cloud.Foreground:nth-child(6) {
animation-duration: 96s;
height: 25%;
}
.Cloud.Background:nth-child(6) {
animation-duration: 126s;
height: 1.59090909%;
}
.Cloud:nth-child(5) {
animation-delay: -92.30769231s;
top: 30%;
}
.Cloud.Foreground:nth-child(5) {
animation-duration: 100s;
height: 22.5%;
}
.Cloud.Background:nth-child(5) {
animation-duration: 130s;
height: 2.84090909%;
}
.Cloud:nth-child(4) {
animation-delay: -73.84615385s;
top: 24%;
}
.Cloud.Foreground:nth-child(4) {
animation-duration: 104s;
height: 20%;
}
.Cloud.Background:nth-child(4) {
animation-duration: 134s;
height: 4.09090909%;
}
.Cloud:nth-child(3) {
animation-delay: -55.38461538s;
top: 18%;
}
.Cloud.Foreground:nth-child(3) {
animation-duration: 108s;
height: 17.5%;
}
.Cloud.Background:nth-child(3) {
animation-duration: 138s;
height: 5.34090909%;
}
.Cloud:nth-child(2) {
animation-delay: -36.92307692s;
top: 12%;
}
.Cloud.Foreground:nth-child(2) {
animation-duration: 112s;
height: 15%;
}
.Cloud.Background:nth-child(2) {
animation-duration: 142s;
height: 6.59090909%;
}
.Cloud:nth-child(1) {
animation-delay: -18.46153846s;
top: 6%;
}
.Cloud.Foreground:nth-child(1) {
animation-duration: 116s;
height: 12.5%;
}
.Cloud.Background:nth-child(1) {
animation-duration: 146s;
height: 7.84090909%;
}
.Cloud {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKQAAABgCAYAAACTzNnjAAAFCklEQVR42u3d34uVRRjA8YMsEi0iSwhdRBGhSJgZiNRFIkWhQVEXBipKUVBBLCF6k0h4UxFkBLq4QT/Qiyi80EgxCjXMWqOMtqy0bBNja92yXatN3c3pGc9sHU/v+X3emWfe93vx+QN23u+e95x5Z94pmNWFQo5NEytErzgoBsSIOCf+FqbEBTEhzophcVTsFxvFEjE152PZHGMukbcB6BAPij0uqgtl0bXCBvyjeFPcS2wEWc0y0ec++Ywnf7rwbyc8grQ6RY8Y9RhhJT+Jde4TmghzFuR08bo4ryDEpE/N58QUYsx+kFPcD4yzCkMsZz+1uwkyu0HeKYYiCLHcN2I2QWYnSPudbHubfy37NuFu4wQZeZD2k2Uw4hDLHRZdBBnnH3K/5ykcX86IeQQZl6civ0XXYv/RlhJkHLZkOMTypz7dBKnbyzmJsfTZeTdB6vRszmIsjXI5QeryWMa/M9ZinzjdTJA63CLGcxzjpD/ElQQZfq3iKDFe8lSHIAPqI8L/6SHIMB4nvorTQQsI0v+teoz4KjpBkH7tIrqaNhCkH9cnbK5C8q/uywiSHzKabCLIdM3K+QR4o8YysfVWcZDvElnD1hNkOi5XuilLu5MlOyzni3vESnGfWCRuIMjm1zgSWPM7GWvNXdonXkdM8Q0dCwmytu8Iy/vK9B3iRoJMnghnqifc8jb7yXkXQf5nLWGo2WR2HUGuLrxPDKq25D6d9yBPE4I6/e6rVO6CnMpkuFq/eLmFKwtyMRde/dOg2XkKkvlH/X5P9ZNSWZBvcMGjMJza6iJlQe7nYkfjozwE+TkXOiprsx7kES5yVOzLYK/IcpA/cJGj806Wg/yaCxzlzsersxpkPxc4SruzGuQBLm603yU7DPOQUOSJWIK0E6j2lcvPm+JJVp+674oDbiFuv5t/fEXs5cJG65DmIO3KY3vG3yCLbXO1N1xVkB3uWfQQFye3rtUS5DN1bC5C9j0cOki7XOwUFwJOb6gg7TmCW1lMizI7QwQ5nW2qqGCv7yDtwsxfGXhU0OczyKvECIOOKvb5CnKa2+TDoKOat3wFyaIH1ONFH0H2MNCo00NpBzmXR39owIy0gzzOIKNOv6X9LHsZg4wGp3xsM/ZMoUfF3aaZ4++qBDnIIKMNzrsHKb11Lb6oEORtDCRSYB81f+vWxzYUJBv2kTYb5vx6gzzHgMHTjsUXagV5BwMFzz78d5NYQpDbGCAEcOzissaEIL9kcBDIx0lBsrwMIW0uD3KcQUHgqaEFpUGyLQGhnSgNkgGBBg8QJDQ5Phkky82g5bvkNQXDscDQY6MNcpiBgBL9NsjDDAS0LPq1Qb7EQECJicLFSUkGAkpMLgMaYzCgKci3GQxomPqZDPImBgMa9uOULifnDWcI7VRpkIsYEAR2sHwX2GcMCgJak/QKPh4lIsgcpOhM2iv7JIODAPqqvY7vPQYInlf6zKsWpN2iOMBAwZMD9bzSudPwvh+kr3gofJ0vve/ikxIp36pXNHosiD2j5gMGD2ksyG3lJK917qc5A4l2eNW04Wi5OaZ41DADilZu0xtMmw/fXCV+ZnDRoFFTfBdpaudlLxVfsXMRNYy7W3SH8XSAu/01bo8s/kL8xQWAM+JC7DIeDnCvZqZ4RGxxi3/3iUPOJ+5TtRXfG/v6jeadNMVjlqsZquG0uwU164ybg2vFuPuh2ax23tkm3N91VLwmbjVNvPT+H7Ro4730ITNPAAAAAElFTkSuQmCC);
}
.Cloud.Background {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEoAAAAqCAYAAAAUJM0rAAACFUlEQVRo3u3aPSwDYRzH8UZEbAYiYrDYjLZGpIPBJLGwSKwi0k1iYBKDdFJsEiMxSZgsFZJGiGjqJWEhSKuaNKRUG8r5PfI8SXOud72+PH2eu2f47Ndv7+V5/nee/G6bh5MWmIV9iEMWvkGjfiADD7AHk9BU6+PSNK0kPAINwwXkC6KU6gvC4HVyKB/clhHHCDnbTqHbSaEaYIv+OK3KyBk274RQrXBXg0B6B/QPkTIUiZTiEIm5gkbZQjXTJ5nG2ZFsocJ1iMQEZQk1VcdIGl2L9Yoeitwj3uocirgRPVRAgEjMoMihkgKFStIzK063QVHYhKF6h/IKFMlKGpaNlhM8Qq1JFIp5gQHeoSIShmJPST/PUElJQ7FN9hivUGmJQxE56OQRKiN5qL/NNY9QKQeEIpdgR7VC9cEqHeGeUyEHXHrMUiWhyKx6BV4dEsNMtNxQI4Ls33h5LidUoEYjXKEXoXZDLbgsEBOzE8qne8fmJhE7oWIujUQcwhyMmy0VSKRRF0cyWlclYMYo1LEKZOgRugpDpVWUot5ZLI8LlwN23bNQKoY1vwpVmmsS6kOFsPRJQl2qENZIqGkVwnp9xb5nUksEcxm2hZlQMcy3OYXTgx0VpOi2pl8/jwqpMP9sF5twBl08ctE7I/dws5l5D5y4OBj51HvdzluYdlik0RL06cjkKpSlB1Span5x/AQb5Hfrxyy/oU5ISeVw53AAAAAASUVORK5CYII=);
}
</style>
</head>
<body>
<div id="Clouds">
<div class="Cloud Foreground"></div>
<div class="Cloud Background"></div>
<div class="Cloud Foreground"></div>
<div class="Cloud Background"></div>
<div class="Cloud Foreground"></div>
<div class="Cloud Background"></div>
<div class="Cloud Background"></div>
<div class="Cloud Foreground"></div>
<div class="Cloud Background"></div>
<div class="Cloud Background"></div>
</div>
<svg version="1.1" id="chmurki" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="40px" height="24px" viewBox="0 0 40 24" enable- xml:space="preserve">
<defs>
<path id="Cloud" d="M33.85,14.388c-0.176,0-0.343,0.034-0.513,0.054c0.184-0.587,0.279-1.208,0.279-1.853c0-3.463-2.809-6.271-6.272-6.271
c-0.38,0-0.752,0.039-1.113,0.104C24.874,2.677,21.293,0,17.083,0c-5.379,0-9.739,4.361-9.739,9.738
c0,0.418,0.035,0.826,0.084,1.229c-0.375-0.069-0.761-0.11-1.155-0.11C2.811,10.856,0,13.665,0,17.126
c0,3.467,2.811,6.275,6.272,6.275c0.214,0,27.156,0.109,27.577,0.109c2.519,0,4.56-2.043,4.56-4.562
C38.409,16.43,36.368,14.388,33.85,14.388z"/>
</defs>
</svg>
<div id="weatherData"></div>
<!--<pre id="rawData"></pre>-->
<script>
document.addEventListener('DOMContentLoaded', function () {
let przedzial_godzinowy = 6; // Ilość kolejnych godzin do wyświetlenia
let pokaz_od_godziny = 'current'; // Pokaż od konkretnej godziny (np. 22:00) lub wpisz 'current'
const citiesData=[
{name:"Białystok",latitude:53.1333,longitude:23.1643},
{name:"Bydgoszcz",latitude:53.1235,longitude:18.0076},
{name:"Gdańsk",latitude:54.3523,longitude:18.6491},
{name:"Gdynia",latitude:54.4981,longitude:18.498},
{name:"Gorzów",latitude:52.7333,longitude:15.25},
{name:"Katowice",latitude:50.2584,longitude:19.0275},
{name:"Kielce",latitude:50.8703,longitude:20.6275},
{name:"Kraków",latitude:50.0614,longitude:19.9366},
{name:"Lublin",latitude:51.25,longitude:22.5667},
{name:"Łódź",latitude:51.7706,longitude:19.4739},
{name:"Olsztyn",latitude:53.7799,longitude:20.4942},
{name:"Opole",latitude:50.6721,longitude:17.9253},
{name:"Poznań",latitude:52.4069,longitude:16.9299},
{name:"Rzeszów",latitude:50.0413,longitude:21.999},
{name:"Szczecin",latitude:53.4289,longitude:14.553},
{name:"Toruń",latitude:53.0138,longitude:18.5981},
{name:"Warszawa",latitude:52.2298,longitude:21.0118},
{name:"Wrocław",latitude:51.1,longitude:17.0333},
{name:"Zielona Góra",latitude:51.9355,longitude:15.5064}
];
const grafika=[
{code:0,day:"d0.svg",night:"n0.svg"},
{code:1,day:"d1.svg",night:"n1.svg"},
{code:2,day:"d2.svg",night:"n2.svg"},
{code:3,day:"d3.svg",night:"n3.svg"},
{code:45,day:"d45.svg",night:"n45.svg"},
{code:48,day:"d48.svg",night:"n48.svg"},
{code:51,day:"d51.svg",night:"n51.svg"},
{code:53,day:"d53.svg",night:"n53.svg"},
{code:55,day:"d55.svg",night:"n55.svg"},
{code:56,day:"d56.svg",night:"n56.svg"},
{code:57,day:"d57.svg",night:"n57.svg"},
{code:61,day:"d61.svg",night:"n61.svg"},
{code:63,day:"d63.svg",night:"n63.svg"},
{code:65,day:"d65.svg",night:"n65.svg"},
{code:66,day:"d66.svg",night:"n66.svg"},
{code:67,day:"d67.svg",night:"n67.svg"},
{code:71,day:"d71.svg",night:"n71.svg"},
{code:73,day:"d73.svg",night:"n73.svg"},
{code:75,day:"d75.svg",night:"n75.svg"},
{code:77,day:"d77.svg",night:"n77.svg"},
{code:80,day:"d80.svg",night:"n80.svg"},
{code:81,day:"d81.svg",night:"n81.svg"},
{code:82,day:"d82.svg",night:"n82.svg"},
{code:85,day:"d85.svg",night:"n85.svg"},
{code:86,day:"d86.svg",night:"n86.svg"},
{code:95,day:"d95.svg",night:"n95.svg"},
{code:96,day:"d96.svg",night:"n96.svg"},
{code:99,day:"d99.svg",night:"n99.svg"}
];
function showWeatherData() {
const weatherDiv = document.getElementById('weatherData');
weatherDiv.style.opacity = 1; // Rozpoczyna się od pełnej widoczności
weatherDiv.classList.add('fade-in');
}
function hideWeatherData(callback) {
const weatherDiv = document.getElementById('weatherData');
weatherDiv.classList.replace('fade-in', 'fade-out');
// Po zakończeniu animacji fade-out, wywołuje podaną funkcję zwrotną
weatherDiv.addEventListener('animationend', function handleAnimationEnd() {
weatherDiv.style.opacity = 0; // Ustawia na nieprzezroczyste
weatherDiv.classList.remove('fade-out');
weatherDiv.removeEventListener('animationend', handleAnimationEnd);
if (callback) callback();
});
}
let currentCityIndex = 0;
function fetchWeatherData() {
if (currentCityIndex >= citiesData.length) {
console.log("Wszystkie miasta zostały przetworzone.");
return; // Stop if all cities have been processed
}
// Przygotowujemy ciągi znaków zawierające odpowiednio szerokości i długości geograficzne
const latitudes = citiesData.map(city => city.latitude).join(',');
const longitudes = citiesData.map(city => city.longitude).join(',');
const city = citiesData[currentCityIndex];
const apiUrl = `https://api.open-meteo.com/v1/forecast?latitude=${city.latitude}&longitude=${city.longitude}¤t=temperature_2m,is_day,weather_code&hourly=temperature_2m,weather_code&daily=weather_code,temperature_2m_max,temperature_2m_min,sunrise,sunset&timezone=Europe%2FBerlin&forecast_days=2`;
fetch(apiUrl)
.then(response => response.json())
.then(data => {
const hourlyData = data.hourly;
const currentTime = new Date(data.current.time);
const closestHourIndex = hourlyData.time.findIndex(time => new Date(time) > currentTime) - 1;
const currentTemperature = hourlyData.temperature_2m[closestHourIndex];
let startIndex = pokaz_od_godziny === 'current' ?
closestHourIndex + 1 :
hourlyData.time.findIndex(time => time.endsWith(pokaz_od_godziny));
startIndex = startIndex === -1 ? 0 : startIndex;
const endIndex = startIndex + przedzial_godzinowy;
const dailyData = data.daily;
// Funkcja do określenia, czy jest dzień czy noc
const isDayTime = (time, dailyData) => {
const timeDate = new Date(time);
for (let i = 0; i < dailyData.time.length; i++) {
const sunriseDate = new Date(dailyData.sunrise[i]);
const sunsetDate = new Date(dailyData.sunset[i]);
if (timeDate >= sunriseDate && timeDate < sunsetDate) {
return 'd';
}
}
return 'n';
};
const hourlyForecast = hourlyData.time.slice(startIndex, endIndex).map((time, index) => {
const dayStatus = isDayTime(time, dailyData);
return {
time,
temperature: hourlyData.temperature_2m[startIndex + index],
weather_code: hourlyData.weather_code[startIndex + index],
day_status: dayStatus // Dodanie statusu dnia/nocy
};
});
const currentWeatherCode = hourlyData.weather_code[closestHourIndex];
const dayOrNight = isDayTime(hourlyData.time[closestHourIndex], dailyData);
// Wyświetlanie danych na stronie
document.getElementById('weatherData').innerHTML = `
<img class="mainicon" src="img/pogoda/${currentWeatherCode}${dayOrNight}.svg" alt="">
<h1>${city.name}</h1>
<h2>${currentTemperature}°C</h2>
<table class="forecastimg">
<tr>
${hourlyForecast.map(entry => `<td class="czas">${entry.time.slice(-5)}</td>`).join('')}
</tr>
<tr>
${hourlyForecast.map(entry => `<td class="prognoza"><img class="iconadd" src="img/pogoda/${entry.weather_code}${entry.day_status}.svg" alt=""></td>`).join('')}
</tr>
<tr>
${hourlyForecast.map(entry => `<td class="temperatura">${entry.temperature}°C</td>`).join('')}
</tr>
</table>
`;
showWeatherData();
setTimeout(() => {
hideWeatherData(() => {
if (++currentCityIndex < citiesData.length) {
fetchWeatherData(); // Pobierz dane dla następnego miasta
}
});
}, 5000);
})
.catch(error => {
console.error('Błąd podczas pobierania danych pogodowych:', error);
document.getElementById('weatherData').innerHTML = '<p>Błąd podczas ładowania danych.</p>';
showWeatherData();
setTimeout(() => {
hideWeatherData(() => {
if (++currentCityIndex < citiesData.length) {
fetchWeatherData(); // Pobierz dane dla następnego miasta
}
});
}, 3000);
});
}
fetchWeatherData();
});
</script>
</body>
</html>