Device Lab от Google: Chromecast 2.0 и Chromecast Audio

в 9:35, , рубрики: android, Chromecast, chromecast audio, Eddystone, github, Google, Google Play, google play music, html5, javascript, Receiver API, SDK, web, web приложения, Беспроводные технологии, мобильные приложения, Программирование, разработка мобильных приложений, Разработка под android

Device Lab от Google: Chromecast 2.0 и Chromecast Audio / Хабрахабр

var N = 5;
var ar_duo1 = Math.floor(Math.random()*N+1);

if (typeof adriver == 'undefined')
{
var adb1 = 'yes';
}

var user_type = "guest";

var page_type = "publish_corp";

(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-726094-1', 'auto');
ga('create', 'UA-726094-24', 'auto', {'name': 'HGM'});

ga('require', 'displayfeatures');
ga('set', 'dimension1', user_type); // user type - guest/readonly/habrauser

ga('set', 'dimension4', adb1);

ga('set', 'dimension5', page_type);

if(typeof removeUtms === 'undefined') {
removeUtms = function(){};
}

ga('HGM.set', 'dimension1', user_type);
ga('HGM.set', 'dimension2', "habrahabr");
ga('HGM.send', 'pageview');

ga('send', 'pageview', { 'hitCallback': removeUtms });

var adcm_config ={
id:1034,
platformId: 34,

tags: ['hub_wireless', 'hub_mobile_dev', 'hub_android_dev', 'hub_programming', 'g_internet_and_telecom', 'g_programming', 'g_mobile_os', 'g_android_os'],
init: function () {
window.adcm.call();
}
};

Одной из важных особенностей экосистемы Android всегда было большое разнообразие устройств. Android работает в смартфонах и планшетах, автомобилях и умных часах, домашней технике и телевизорах. На нынешнем I/O были анонсированы и новинки - умная колонка Home, платформа виртуальной реальности Daydream. Android все больше проникает во все сферы цифровой жизни, помогая управлять любыми устройствами.

Device Lab от Google: Chromecast 2.0 и Chromecast Audio - 1

Этой статьей мы начинаем цикл, в котором хотим рассказать о том, как использовать разные устройства экосистемы Android в своих приложениях: как транслировать видео на Chromecast, как создавать приложения для Android TV или как использовать мир "физического веба" в маячками Eddystone.

Самое главное - не забывайте про сам конкурс - Device Lab от Google. Вы сможете взять любые из этих устройств и начать разрабатывать свои приложения, тестируя функционал вживую.

Добро пожаловать в будущее Android.

Android и Media

В этой статье мы рассмотрим вопросы работы и создания приложений для медиа-устройств - Chromecast и Chromecast Audio. В рамках нашего проекта все они доступны для разработчиков - оставьте заявку и начните работу с ними прямо сейчас.

Chromecast Audio

Chromecast Audio - миниатюрное устройство для трансляции аудио-потока на любое устройство с AUX входом, вышедшее в продажу в конце 2015 года. Идеальный подход Chromecast Audio как раз в том, что вы можете транслировать вашу любимую музыку практически на любое устройство с таким входом - ваш старый музыкальный центр получит интеграцию с Google Play Music благодаря подключению к Chromecast Audio и через него уже к вашему смартфону или планшету. Недорогой гаджет - ответ на все чаяния тех, кому не хочется обновлять свои аудиосистемы, но хочется сделать их умными и получить все возможности современных приложений у себя дома.

Device Lab от Google: Chromecast 2.0 и Chromecast Audio - 2

Поставляется Chromecast Audio в миниатюрной коробке, в комплекте у него блок питания с выходом microUSB (фактически вы можете подключить его к любому устройству с таким шнуром, не привязываясь к розетке), собственно сам Chromecast Audio и кабель-переходник. Подключение его к аудиосистеме очень простое - вы втыкаете кабель в Chromecast Audio, скачиваете приложение в Google Play для управления и все работает - на подключение к колонкам у меня ушло около 5 минут с обновлением прошивки (вы также можете подключить Chromecast Audio к своей Wi-Fi сети и устройство сможет работать в ней автономно).

После этого в приложениях, которые поддерживают трансляцию (есть на официальном сайте Chromecast), вы сможете выбрать нужное устройство. Каждое из них можно назвать по-своему и организовать передачи звука в разные комнаты своей квартиры или дома, таким образом зонировать и «перемещать» трансляции. Точно так же - управлять трансляциями голосом при помощи только что анонсированного на I/O Google Home: «Включи музыку в гостиной - Переключи в спальню - Выключи».

Посмотрим на примере Google Play Музыки, как в два шага открывается трансляция на Chromecast Audio:

$(document).ready(function() {
t341_showCaptions('5839448');
t341_checkSize('5839448');
$("#t-carousel5839448 .t-carousel__slides").swipe( {
swipeLeft:function(event, direction, distance, duration) {
$(this).parent().carousel('next');
},
swipeRight: function(event, direction, distance, duration) {
$(this).parent().carousel('prev');
},
threshold: 50,
preventDefaultEvents: false,
allowPageScroll: "none"
});
});

$(window).resize(function() {
t341_checkSize('5839448');
});

Пожалуй, главная проблема Chromecast Audio в том, что трансляция не работает "по умолчанию" в любом приложении. Хотя их несколько десятков уже подключенных, все же, если вы Spotify, скажем, предпочитаете Zvooq, то завести его на Chromecast Audio у вас не получится - последнее приложение пока, к сожалению, не поддерживает такую трансляцию.

Но ведь затем мы здесь и собрались - чтобы расширить набор приложений, работающих с удобными устройствами.

Chromecast

Chromecast - старшее устройство, цифровой медиаплеер от Google, предназначенный уже для потокового воспроизведения видео контента (кстати, Сундар Пичаи в ходе открывающего I/O выступления отметил, что всего в мире продано уже более 25 миллионов устройств Chromecast). Chromecast работает на упрощенной версии Chrome OS. Для передачи медиа-контента Chromecast использует протокол DIAL, разработанный совместными усилиями компаний Netflix и YouTube. Главная его особенность в том, что, в отличие от других подобных технологий, устройство пользователя не участвует в обработке потока, оно фактически инициирует передачу, а дальше весь контент Chromecast (как в случае аудио, так и видео) берет с удаленного сервера сам. Таким образом телефон или планшет исключается из процесса обработки, не тратя свои ресурсов, не выжигая батарею и не нагружая сеть, он может заниматься другими задачами.

Device Lab от Google: Chromecast 2.0 и Chromecast Audio - 3

Большой Chromecast подключается уже не к аудиопорту, как Chromecast Audio, а к стандартному HDMI, то есть его можно "повесить" на телевизор, даже не подключенный к Сети, и передавать контент на него. Если Chromecast Audio это улучшение аудио систем, то Chromecast очевидно предназначен для видео-систем и создания из них современных мультимедийных центров, переноса новых приложений и трансляции веб-контента на экранах домашних телевизоров.

Настройка Chromecast происходит через то же самое приложение Google Cast - оно подключается к брелоку, и вы можете задать параметры доступа к Wi-Fi сети. После этого Chromecast сам скачает все обновления и вы сможете начать работу с ним.

Для этого вам снова понадобится приложение, которое умеет транслировать контент на Chromecast. Для примера возьмем YouTube для iOS, в нем появляется иконка, при нажатии на которую начинается трансляция:

Device Lab от Google: Chromecast 2.0 и Chromecast Audio - 4

При этом вы сможете продолжить работу с другими приложениями, так как трансляция идёт в фоновом режиме:

Device Lab от Google: Chromecast 2.0 и Chromecast Audio - 5

Вы также можете транслировать содержимое браузера на ТВ, для Chrome есть расширение Google Cast. Хотите изучать Хабр на большом экране? Да пожалуйста!

Единственное «но», как и в случае с Chromecast Audio - приложение должно поддерживать трансляцию на экран, с любым приложением по умолчанию это не работает. Но, надо сказать, приложений таких уже достаточно много, есть TED, Амедиатека, ivi.ru и Zoombye, не говоря уж о «стандартных» YouTube, Google Play Фильмах и т.п.

Есть также некоторые простые игры, вроде Angry Birds Go. Список весь вы можете найти тут. Для Android начиная с версии 4.4.2 доступна трансляция всего экрана устройства на Chromecast (зеркалирование), для этого в меню приложения есть специальный пункт.

var div=$("#youtubeiframe5838599");
var height=div.width() * 0.5625;
div.height(height);
div.parent().height(height);

Мне, правда, заставить эту функцию работать удалось лишь на мгновенье, но я отношу это к недостаткам моего тестового смартфона.

Кстати, в режиме простоя Chromecast транслирует на экран какие-то фантастически красивые фотографии природы, так что выключать его даже не хочется (вместо них на заставку можно поставить и свои собственные фотографии, все тоже через приложение).

Google Cast SDK

Начало вашей интеграции - Google Cast SDK с библиотеками и примерами кода для Android, iOS или Chrome.

Компоненты, которые вам нужно создать:

В общем виде вам понадобится отправляющее поток приложение (sender application). Обычно это приложение на мобильном устройстве, в котором ведется трансляция.

Примеры:

Обратите внимание, что в конкурсе разработчиков участвуют только Android и Web-приложения

Принимающее поток приложение (receiver application). Оно управляет коммуникациями между отправляющим приложением и сами устройством. В случае аудио вам принимающее приложение не нужно, в случае видео у вас есть несколько вариантов действия. Вы можете использовать:

  • Default Media Receiver со стандартным брендингом Google Cast;
  • cвой собственный Styled Media Receiver, отличающийся дизайном;
  • кастомный ресивер, опирающийся на Receiver API и обрабатывающий специальные сообщения от вашего отправляющего приложения.

Обратите внимание, что вам понадобится дополнительная регистрация вашего аккаунта и устройств в Google для работе с Cast SDK. Стоит она 5 долларов. После регистрации вам выдадут ID, который можно будет использовать в вашем приложении для работы с устройствами.

Также настоятельно рекомендуем вам ознакомиться со списком проверки, который необходимо пройти каждому Cast-приложению. В нем перечислены все важные пункты, касающиеся дизайна и юзабилити приложений, транслирующих видео или аудио на устройства Chromecast.

Отправка

Рассмотрим обычную процедуру работы приложения с Cast-устройством:

  • Приложение начинает обнаружение MediaRouter: MediaRouter.addCallback
  • MediaRouter информирует посылающее приложение о выбранном пользователем устройстве: MediaRouter.Callback.onRouteSelected
  • Приложение получает инстанс CastDevice: CastDevice.getFromBundle
  • Приложение создает GoogleApiClient: GoogleApiClient.Builder
  • Приложение подключается к GoogleApiClient: GoogleApiClient.connect
  • SDK подтверждает, что GoogleApiClient подключен: GoogleApiClient.ConnectionCallbacks.onConnected
  • Транслирующее приложение запускает принимающее приложение: Cast.CastApi.launchApplication
  • SDK подтверждает, что принимающее приложение подключено: ResultCallback<Cast.ApplicationConnectionResult>
  • Транслирующее приложение создает коммуникационный канал: Cast.CastApi.setMessageReceivedCallbacks
  • Транслирующее приложение отправляет сообщение на ресивер через коммуникационный канал: Cast.CastApi.sendMessage

Более подробно с примерами кода по каждому из пунктов вы можете прочитать на официальной странице.

Пример приложения: https://github.com/ckurtm/LocalCast

Инициализируем конфигурацию:

CastConfiguration options =new CastConfiguration.Builder("84B70D9D")
	.enableAutoReconnect()
.enableDebug()
.build(); 
DataCastManager.initialize(this,options);

$(document).ready(function(){
hljs.initHighlightingOnLoad();
});

.t264 .hljs {
background-color: ;
}

Делаем Cast-кнопку:

<item android:id="@+id/action_cast" 
android:title="@string/action_cast" app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider" app:showAsAction="always"/> 

@Override 
public boolean onCreateOptionsMenu(Menu menu)
{ 
super.onCreateOptionsMenu(menu); 
getMenuInflater().inflate(R.menu.menu,menu); DataCastManager.getInstance().addMediaRouterButton(menu,R.id.action_cast); 
return true; 
}

$(document).ready(function(){
hljs.initHighlightingOnLoad();
});

.t264 .hljs {
background-color: ;
}

Отправляем данные:

JSONObject obj = new JSONObject(); 
String url = "http://"+ info.ip + ":" + info.port + "/" + item.getFile().getAbsolutePath(); 
Log.d(TAG, "casting: " + url); 
try
{ 
obj.put("url",url); 
DataCastManager.getInstance().sendDataMessage(obj.toString(),"urn:x- cast:com.peirr.localcast");
 } 
catch (JSONException|IOException e) 
{ 
e.printStackTrace(); 
}

$(document).ready(function(){
hljs.initHighlightingOnLoad();
});

.t264 .hljs {
background-color: ;
}

Приём

Приложение-приемник это HTML5/JavaScript-приложение (это справедливо для Chromecast, отличие Android TV в том, что там как раз работают полноценные приложения, а тут используется тонкий веб-клиент), которое запускается на целевом устройстве (Chromecast) и предоставляет интерфейс для показа контента на экране ТВ, обеспечивает обработку сообщений для управления контентом и специальных сообщений, которые может посылать клиент.

Решений у вас, соответственно, всего два - стилизовать стандартный Media Receiver (вам понадобится собственный CSS-файл) или разработать собственный с нуля. Последнее может понадобиться, если вы хотите показывать нестандартный контент, который не поддерживает стандартный ресивер.

Как выбрать? Google разработал превосходную схему:

Device Lab от Google: Chromecast 2.0 и Chromecast Audio - 6

Простой кастомный ресивер:

<head> 
<title>LocalCast</title> 
<script src="cast_receiver.js"></script>
<link rel="stylesheet" href="receiver.css"/>
</head>

<script> 

function process(json)
{
console.log('received: ' + json.url);
document.getElementById("image").src= json.url;
 } 

window.castReceiverManager = cast.receiver.CastReceiverManager.getInstance(); castReceiverManager.onSenderDisconnected = function(event)
{ 
console.log('disconnected: ' + event.data); 
if (window.castReceiverManager.getSenders().length == 0) { //close the app if we have no more connected devices window.close(); }
}; 

window.messageBus = window.castReceiverManager.getCastMessageBus('urn:x-cast:com.peirr.localcast'); 
window.messageBus.onMessage = function(event)
{ 
var json = JSON.parse(event['data']); //decode the request from sender app 
window.sender = event.senderId; process(json); 
} 

window.castReceiverManager.start(); 

</script>

$(document).ready(function(){
hljs.initHighlightingOnLoad();
});

.t264 .hljs {
background-color: ;
}

Дополнительные материалы

Бесплатный курс на Udacity "Разработка для Android TV и Google Cast":

https://www.udacity.com/course/android-tv-and-google-cast-development--ud875B

Плагин для Unity:

https://www.assetstore.unity3d.com/en/#!/content/50168

Репозиторий Chromecast на GitHub:

https://github.com/googlecast/

Разработка игр для Google Cast:

var div=$("#youtubeiframe5839810");
var height=div.width() * 0.5625;
div.height(height);
div.parent().height(height);

Разработка для Chromecast, презентация Курта Мбанье, ведущего Android разработчика в DStv Online:

http://www.slideshare.net/ckurtm/developing-for-chromecast-on-android-61200612

Советы от разработчика

Device Lab от Google: Chromecast 2.0 и Chromecast Audio - 7

Илья Манин
разработчик SPB TV

— Как у вас работает трансляция на Chromecast?

— Мы используем стандартную технологию трансляции видео для Chromecast, которая не так давно получила название Google Cast. Эта технология интересна тем, что непосредственно сам видео-контент не транслируется с мобильного устройства на Chromecast. Мобильное устройство передает только ссылку на поток, а загрузкой видео и декодированием занимается уже само Chromecast устройство. Причем, такой метод трансляции работает не только с Chromecast, но и с любым устройством, поддерживающим Google Cast технологию. На текущий момент это все устройства на базе Android TV - STB-приставки, например Nexus Player или NVidia Shield и Smart TV телевизоры от Sony и Philips.

Такой подход позволяет освободить вычислительные мощности мобильного устройства, оно не будет перегреваться и разряжать аккумулятор. Трансляция будет продолжаться даже при выключенном телефоне. Более того, можно продолжить управлять этой трансляцией с любого другого Android или iOS-устройства или из настольного браузера Chrome.

Другие технологии, такие, как Miracast или WiDi, используют «зеркалирование» экрана, т.е. на устройстве в реальном времени происходит захват видеопотока с экрана устройства, кодирование видео и трансляция по Wi-Fi на приемные устройства. По нашему опыту, такие технологии требовательны к ресурсам мобильных устройств и нагружают локальную Wi-Fi сеть. Для «зеркалирования» онлайн-трансляций поток сначала скачивается на мобильное устройство, там декодируется и выводится на экран, затем снова кодируется и передается на приемник по той же Wi-Fi сети, на приемнике видеопоток снова декодируется и выводится на экран. Недостатки такого подхода особенно явно проявляются при трансляции тяжелого HD-контента.

Строго говоря, Google Cast приемники также поддерживают «зеркалирование». Но необходимо учитывать, что этот режим долгое время был в состоянии бета версии, Google не советует использовать его для видеовещания, и работа в таком режиме возможна не со всех мобильных устройств.

«Зеркалирование» может использоваться для вещания игр или для приложений, не поддерживающих основной режим работы Google Cast, однако, в этом случае могут возникать задержки (latency), вызывающие дискомфорт в динамичных играх.

Во всех остальных случаях мы советуем использовать трансляцию Google Cast, а разработчикам советуем адаптировать приложения для полноценной поддержки этой технологии.

— Вызвала ли какие-нибудь проблемы интеграция?

— Для реализации минимальной функциональности никаких проблем не возникает. По этой технологии Google подготовил очень подробную документацию, доступно много рабочих примеров и библиотек. Некоторые сложности проявляются, когда мобильные программисты понимают, что приложение-приемник пишется на JavaScript. Но для простых задач можно использовать готовый приемник от Google, тогда необходимость программировать на JS и поддерживать свое Web-приложение полностью отпадает.

Также могут возникнуть сложности, когда стоит задача реализовать полную функциональность по всем Guidelines от Google. В таких случаях приходится учитывать большой объем различных факторов, особенно по UI. Нужно интегрировать Сast компонент в лайауты всех экранов, обеспечить работу на экране блокировки и в области нотификации, необходимо вовремя получать события от приемника, грамотно их обрабатывать и обновлять UI.

К счастью, уже появились библиотеки, частично решающие эти задачи, например, CastCompanionLibrary (https://github.com/googlecast/CastCompanionLibrary-android).

Однако пока подобные библиотеки не охватывают все возможные случаи, и во многом требуют доработки.

Вот пример кода, который запускает трансляцию на Chromecast:

import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaMetadata;
import com.google.android.gms.common.images.WebImage;
import com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager;
...
private final VideoCastManager mVideoCastManager = VideoCastManager.getInstance();
...
public void play() {
MediaMetadata contentMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_GENERIC);
// название видео, будет отображаться на ТВ
contentMetadata.putString(MediaMetadata.KEY_TITLE, "Тестовый HLS поток");
// постер для видео (например постер фильма)
String posterUrl = "http://.../link_to_poster_image.jpg";
// небольшой логотип (например логотип ТВ канала)
String logoUrl = "http://.../link_to_logo_image.jpg";
WebImage poster = new WebImage(posterUrl);
WebImage logo = new WebImage(logoUrl);
contentMetadata.addImage(poster);
contentMetadata.addImage(logo);
// будем смотреть живое вещание
int streamType = MediaInfo.STREAM_TYPE_LIVE;
// ссылка на демо поток
String streamUrl = "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8";
MediaInfo info = new MediaInfo.Builder(streamUrl).setContentType("application/x-mpegURL")
.setStreamType(streamType)
.setMetadata(contentMetadata)
.build();
boolean autoplay = true;
int position = 0;
mVideoCastManager.loadMedia(info, autoplay, 0);
}

$(document).ready(function(){
hljs.initHighlightingOnLoad();
});

.t264 .hljs {
background-color: ;
}

— Какими ресурсами вы пользовались при разработке?

— Основной ресурс, где собрано все, что нужно для программирования на ChromeCast - UI-гайды, чеклисты, документация для разработчиков, подробные описания и примеры: https://developers.google.com/cast/

Подборка примеров и библиотек на GitHub: https://github.com/googlecast/

Будущее

На самом деле, медиа-устройства Google оказались намного интереснее и функциональнее, чем казалось изначально. Они решают насущные проблемы, причем удобными и простыми способами. Используйте их в разработке - и ваши приложения приобретут новую аудиторию и новых благодарных пользователей.

Участвуйте в конкурсе Device Lab от Google, внедряйте поддержу новых устройств в ваши приложения, это и есть будущее, к которому вы уже можете прикоснуться.

А в следующей статье лаборатории мы рассмотрим Android TV.

Комментарии (0)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

$(document).ready( function(){
window.tmidLogin = function(){ return false; };
if( $.cookie('tmid_no_check') === undefined ) {
var expire = new Date();
expire.setMinutes(expire.getMinutes() + 10 );
$.cookie('tmid_no_check', 1, { expires: expire } );
$.getScript("https://id.tmtm.ru/checklogin/", function(){
if( window.tmidLogin() ) {
var href = $('#login').attr('href');
if( href !== undefined ) {
window.location.href = href;
}
}
});
}
});

// global vars
var g_base_url = 'habrahabr.ru';
var g_show_xpanel = false;
var g_base_fullurl = 'https://habrahabr.ru/';
var g_is_guest = false;

(function (d, w, c) {
(w[c] = w[c] || []).push(function() {
try {
if (typeof (_yaparams) != 'undefined') {
w.yaCounter24049213 = new Ya.Metrika({id:24049213,
webvisor:true,
clickmap:true,
trackLinks:true,
accurateTrackBounce:true,
params:_yaparams});
} else {
w.yaCounter24049213 = new Ya.Metrika({id:24049213,
webvisor:true,
clickmap:true,
trackLinks:true,
accurateTrackBounce:true});
}

} catch(e) { }
});

var n = d.getElementsByTagName("script")[0],
s = d.createElement("script"),
f = function () { n.parentNode.insertBefore(s, n); };
s.type = "text/javascript";
s.async = true;
s.src = (d.location.protocol == "https:" ? "https:" : "http:") + "//mc.yandex.ru/metrika/watch.js";

if (w.opera == "[object Opera]") {
d.addEventListener("DOMContentLoaded", f, false);
} else { f(); }
})(document, window, "yandex_metrika_callbacks");

Device Lab от Google: Chromecast 2.0 и Chromecast Audio - 8

function checkHeaderPos(){
var topToHubs = $('.megapost-cover').offset().top + $('.megapost-cover').outerHeight();
var sT = $(this).scrollTop();
if (sT > topToHubs) {
$('.t199__js__header, .t199_js__header').fadeIn();
} else {
$('.t199__js__header, .t199_js__header').fadeOut();
}
}

$(window).on('scroll', function() {
checkHeaderPos();
});

function drawguides(){
if($("#guides").length)$("#guides").remove();

$("body").append('

');

var g=$('#guides');
var ww=$(window).width();

var offset_left=parseInt((ww-1200)/2);
var col_space=20;
var col_width=100;

if(ww=960){
for(i=0;i<13;i++){
var x1=(i*col_width)+offset_left-col_space;
var x2=(i*col_width)+offset_left+col_space;
var n=i+1;
if(i!=0)g.append('

');
if(i!=12)g.append('

');
if(i!=12)g.append('

'+n+'

');
}
}

if(ww<960){
var x=parseInt(ww/2);
g.append('

');
}

var doit;
$(window).resize(function() {
if($("#guides").length){
clearTimeout(doit);
doit = setTimeout(drawguides, 300);
}
});
}

function showguides(){
if($("#guides").length){
$("#guides").remove();
$("#guidesmenubutton").css("display","none");
}else{
drawguides();
$("#guidesmenubutton").css("display","block");
}
}

Автор:

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js