Django a Ajax:Vytvoření aplikace pro živé nahrávání Django

Motivace

Nedávno jsem pracoval na sekci Q a A webové aplikace. A požadavky nařizovaly, že uživatelům by měla být poskytnuta možnost nahrávat otázky živě v angličtině nebo v jakémkoli jiném podporovaném jazyce. Nejen to, centrum zákaznické podpory by mělo mít stejnou výsadu odpovídat živým záznamem odpovědí. Při procházení webu po některých řešeních jsem narazil na Záznam zvuku v modelu Django, ale odezva je nějak zastaralá. Rozhodl jsem se znovu implementovat funkční příklad pomocí technologií, které navrhl.

Technologie

  • Django
  • Videojs-record
  • Ajax
  • HTML
  • Bulma CSS

Předpoklady/předpoklady

Za prvé, předpokládá se, že Djanga znáte. Protože budeme hodně používat Ajax a JavaScript, měli byste mít pracovní znalosti JavaScriptu. Pro prezentaci bude použit Bulma CSS, i když není vyžadován, znalost frameworku je skvělá.

Zdrojový kód

Úplný kód pro tento článek je na github a je přístupný přes:

Nahrávání Django Ajax

Toto je navazující úložiště pro výukový program pro živé nahrávání na dev.to

Spouštět lokálně

Pro spuštění lokálně

  • Klonujte toto úložiště:
     git clone https://github.com/Sirneij/django-ajax-record.git
    
  • Změňte adresář do složky:
     cd django-ajax-record
    
  • Vytvořte virtuální prostředí:
     virtualenv -p python3.8 env
    
    Můžete se rozhodnout pro jiné nástroje pro správu závislostí, jako je pipenv nebo venv . Je to na vás.
  • Aktivujte prostředí:
    • Pro počítače se systémem Linux a Mac
      source env/bin/activate
      
    • Pro počítač se systémem Windows:
      .\env\Scripts\activate
      
  • Nainstalujte závislosti:
    pip install -r requirements.txt
    
  • Upravit core/models.py pokud nepoužíváte Cloudinary jako službu úložiště.
    • Od
      voice_record = models.FileField(upload_to="records", storage=RawMediaCloudinaryStorage())
    
    • Komu
      voice_record = models.FileField(upload_to="records")
    
  • Proveďte migraci a migrujte databázi:
     python manage.py makemigrations
     python manage.py migrate
    
  • Nakonec spusťte aplikaci:
     python manage.py runserver
    
    Navštivte ve svém prohlížeči http://localhost:8000

Živá verze

Tato aplikace je aktuálně aktivní zde


Zobrazit na GitHubu

Jako obvykle je aktuálně živě na django-record.herokuapp.com (prozatím je zde chyba úložiště 🐛)

Krok 1 – Nastavení projektu

Spusťte svůj terminál, vytvořte adresář pro umístění projektu, aktivujte virtuální prostředí a nainstalujte django.

┌──(sirneij@sirneij)-[~/Documents/Projects/Django]
└─$[sirneij@sirneij Django]$ mkdir django_record && cd django_record


┌──(sirneij@sirneij)-[~/Documents/Projects/Django/django_record]
└─$[sirneij@sirneij django_record]$ virtualenv -p python3.8 env

┌──(sirneij@sirneij)-[~/Documents/Projects/Django/django_record]
└─$[sirneij@sirneij django_record]$ source env/bin/activate


(env) ┌──(sirneij@sirneij)-[~/Documents/Projects/Django/django_record]
└─$[sirneij@sirneij django_record]$ pip install django

Krok 2 — Spuštění projektu Django

Po instalaci django spusťte nový projekt a poté aplikaci.

(env) ┌──(sirneij@sirneij)-[~/Documents/Projects/Django/django_record]
└─$[sirneij@sirneij django_record]$ django-admin startproject record .


(env) ┌──(sirneij@sirneij)-[~/Documents/Projects/Django/django_record]
└─$[sirneij@sirneij django_record]$ django-admin startapp core

Krok 3 – Přidejte aplikaci do svého projektu

Otevřete vytvořený projekt v textovém editoru nebo IDE dle výběru (já se držím kódu Visual Studio) a přejděte na settings.py vašeho projektu soubor. V souboru vyhledejte INSTALLED_APPS a připojte k ní vytvořenou aplikaci takto:

# record > settings.py
...

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",

    #Add the created app
    "core.apps.CoreConfig",
]

...

Vytvořte urls.py v core složku app a vložte do ní následující:

# core > urls.py
from django.urls import path

app_name = "core"

urlpatterns = []

Přejděte do urls.py svého projektu soubor a vypadat takto:

# record > urls.py
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path
from django.urls.conf import include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("core.urls", namespace="core")), # this adds a namespace to our core app using its urls.py file
]

if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Tyto řádky:

...
if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

instruovat django, aby obsluhovalo tyto soubory (statické a mediální), když DEBUG=True (tj. během vývoje)

Krok 4 – Konfigurace šablon, statických a mediálních adresářů

Protože budeme používat spoustu šablon, statických a mediálních souborů, nakonfigurujte adresáře, které by pro ně měl django hledat. Nezapomeňte vytvořit tyto složky v kořenovém adresáři vašeho projektu.

...

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "templates"], #Add template directory her
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]

...

STATIC_URL = "/static/"
STATICFILES_DIRS = (BASE_DIR / "static",)
STATIC_ROOT = BASE_DIR / "staticfiles"
STATICFILES_FINDERS = [
    "django.contrib.staticfiles.finders.FileSystemFinder",
    "django.contrib.staticfiles.finders.AppDirectoriesFinder",
]


MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"

...

Vytvořte templates , static a media adresáře.

(env) ┌──(sirneij@sirneij)-[~/Documents/Projects/Django/django_record]
└─$[sirneij@sirneij django_record]$ mkdir -p templates static media

Krok 5 – Přidání indexového zobrazení

Chcete-li otestovat naše dosavadní nastavení, přejděte na views.py vaší aplikace a připojte následující:

# core > views.py
...

def index(request):
    context = {
        "page_title": "Voice records",
    }
    return render(request, "core/index.html", context)

Je to jednoduchý Function Based View(FBV) který vykresluje jednoduchou šablonu index.html, která ještě nebyla vytvořena který se nachází v core adresář templates adresář. Před vytvořením tohoto adresáře a html propojme jej s urls.py soubor.

# core > urls.py

from django.urls import path

from . import views

app_name = "core"

urlpatterns = [
    path("", views.index, name="index"),
]


Nyní vytvořte core podadresář v templates složku a připojte index.html k tomu. Předtím ale pojďme pracovat na souboru rozvržení pro celou aplikaci. Jmenuji to _base.html .

(env) ┌──(sirneij@sirneij)-[~/Documents/Projects/Django/django_record]
└─$[sirneij@sirneij django_record]$ touch templates/_base.html


(env) ┌──(sirneij@sirneij)-[~/Documents/Projects/Django/django_record]
└─$[sirneij@sirneij django_record]$ mkdir templates/core && touch templates/core/index.html

Otevřete tyto soubory a nechte je vypadat takto:

<!--templates > _base.html-->
{% load static %}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Django Ajax - {% block title %}{% endblock title %}</title>
    <link rel="stylesheet" href="{% static 'assets/css/bulma.min.css' %}" />
  </head>
  <body>
    {% block content %} {% endblock content %}
  </body>
</html>

Toto _base.html byl zkopírován ze šablony Bulma CSS Starter a byly provedeny některé úpravy. Všimněte si, že nepoužívám Bulma CSS CDN. Dávám přednost poskytování služeb static soubory lokálně, abyste snížili počet síťových volání.

Nyní k index.html :

<!--templates > core > index.html -->

<!--inherits the layout-->
{% extends '_base.html' %}
<!--passes the page title-->
{% block title %}{{page_title}}{% endblock title %}
<!--content starts-->
{% block content %}
<section class="section">
  <div class="container">
    <h1 class="title">Hello World</h1>
    <p class="subtitle">My first website with <strong>Bulma</strong>!</p>
  </div>
</section>
{% endblock content %}

Komentáře mluví za vše.

Je čas to otestovat! Otevřete terminál a runserver !

(env) ┌──(sirneij@sirneij)-[~/Documents/Projects/Django/django_record]
└─$[sirneij@sirneij django_record]$ python manage.py runserver

Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
July 16, 2021 - 19:09:00
Django version 3.2.5, using settings 'record.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Varování zatím zanedbejte. Otevřete prohlížeč a navštivte http://127.0.0.1:8000/ .

Od této chvíle nebudu o HTML moc mluvit a CSS .

Krok 6 – Vytvořte model a logiku zobrazení

Nyní k první polovině skutečného obchodu. Pojďme vytvořit jednoduchý model pro uložení nahraných zvuků a přidat logiku zobrazení pro vystavení POST API pro záznam tak, že Ajax může konzumovat později.

# core > models.py

import uuid

from django.db import models
from django.urls.base import reverse


class Record(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    voice_record = models.FileField(upload_to="records")
    language = models.CharField(max_length=50, null=True, blank=True)

    class Meta:
        verbose_name = "Record"
        verbose_name_plural = "Records"

    def __str__(self):
        return str(self.id)

    def get_absolute_url(self):
        return reverse("record_detail", kwargs={"id": str(self.id)})


Model je prostě normální. Vždy musím přepsat výchozí BigAutoField django dává id . Preferuji UUID pole. Kromě toho má tabulka pouze dvě pole:voice_records a language který je volitelný. Naše nahrávky budou uloženy v records podadresář media adresář.

Udělejte si views.py soubor se zobrazí následovně:

# core > views.py

from django.contrib import messages
from django.http.response import JsonResponse
from django.shortcuts import get_object_or_404, render

from .models import Record


def record(request):
    if request.method == "POST":
        audio_file = request.FILES.get("recorded_audio")
        language = request.POST.get("language")
        record = Record.objects.create(language=language, voice_record=audio_file)
        record.save()
        messages.success(request, "Audio recording successfully added!")
        return JsonResponse(
            {
                "success": True,
            }
        )
    context = {"page_title": "Record audio"}
    return render(request, "core/record.html", context)


def record_detail(request, id):
    record = get_object_or_404(Record, id=id)
    context = {
        "page_title": "Recorded audio detail",
        "record": record,
    }
    return render(request, "core/record_detail.html", context)


def index(request):
    records = Record.objects.all()
    context = {"page_title": "Voice records", "records": records}
    return render(request, "core/index.html", context)


record funkce zpřístupní vytvoření nahrávky a poté ji uloží. Pro detailní zobrazení record_detail zpracovává pouze jeden záznam a naše index vypíše všechny dostupné nahrávky v databázi.

Pojďme všechny tyto změny promítnout do urls.py naší aplikace soubor.

# core > urls.py

from django.urls import path

from . import views

app_name = "core"

urlpatterns = [
    path("", views.index, name="index"),
    path("record/", views.record, name="record"),
    path("record/detail/<uuid:id>/", views.record_detail, name="record_detail"),
]


Je čas databázi skutečně vytvořit, aby tabulka mohla existovat. Chcete-li to provést, jednoduše spusťte migrations ve vašem terminálu.

(env) ┌──(sirneij@sirneij)-[~/Documents/Projects/Django/django_record]
└─$[sirneij@sirneij django_record]$ python manage.py makemigrations


(env) ┌──(sirneij@sirneij)-[~/Documents/Projects/Django/django_record]
└─$[sirneij@sirneij django_record]$ python manage.py migrate

Měli byste být přivítáni něčím, co vypadá jako:

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, core, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying core.0001_initial... OK
  Applying sessions.0001_initial... OK

Krok 7 – Představení videojs-record a ajax

Je čas něco opravdu natočit. K tomu potřebujeme spoustu .js soubory a několik .css . jQuery bude potřeba také pro ajax . V úplné verzi projektu jsou zahrnuty všechny tyto soubory, ale níže je několik výňatků:

<!-- templates > _base.html -->

{% load static %}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Django Ajax - {% block title %}{% endblock title %}</title>
    <link rel="stylesheet" href="{% static 'assets/css/bulma.min.css' %}" />
    {% block css %}{% endblock css %}
  </head>
  <body>
    <!--header-->
    {% include 'includes/_header.html' %}
    <!--content-->
    {% block content %} {% endblock content %}
    <!-- js-->
    <script src="{% static 'assets/js/jquery.min.js' %}"></script>
    <script>
      const triggerModal = document.getElementById("triggerModal");
      triggerModal.style.display = "none";
      const csrftoken = $("[name=csrfmiddlewaretoken]").val();
      if (csrftoken) {
        function csrfSafeMethod(method) {
          // these HTTP methods do not require CSRF protection
          return /^(GET|HEAD|OPTIONS|TRACE)$/.test(method);
        }
        $.ajaxSetup({
          beforeSend: function (xhr, settings) {
            if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
              xhr.setRequestHeader("X-CSRFToken", csrftoken);
            }
          },
        });
      }
    </script>
    {% block js %}{% endblock js %}
  </body>
</html>

Tato část:

...
      const csrftoken = $("[name=csrfmiddlewaretoken]").val();
      if (csrftoken) {
        function csrfSafeMethod(method) {
          // these HTTP methods do not require CSRF protection
          return /^(GET|HEAD|OPTIONS|TRACE)$/.test(method);
        }
        $.ajaxSetup({
          beforeSend: function (xhr, settings) {
            if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
              xhr.setRequestHeader("X-CSRFToken", csrftoken);
            }
          },
        });
      }
...

pomáhá získat csrf tokens z formuláře, který budeme zpracovávat později, aniž bychom explicitně zahrnuli jeho hodnotu do všech ajax POST hovory. To je velmi užitečné v aplikacích s mnoha formuláři, které budou zpracovány pomocí ajax .

Nyní k templates/core/record.html .

<!-- templates > core > record.html -->

<!--inherits the layout-->
{% extends '_base.html' %}
<!--static-->
{% load static %}
<!--title-->
{% block title %}{{page_title}}{% endblock title %}

<!--additional css-->

{% block css %}
<link href="{% static 'assets/css/video-js.css' %}" rel="stylesheet" />
<link href="{% static 'assets/css/all.min.css' %}" rel="stylesheet" />
<link
  href="{% static 'assets/css/videojs.wavesurfer.min.css' %}"
  rel="stylesheet"
/>
<link href="{% static 'assets/css/videojs.record.css' %}" rel="stylesheet" />
<style>
  /* change player background color */
  #createQuestion {
    background-color: #198754;
  }
</style>
{% endblock css %}
<!--content-->
{% block content %}
<section class="section">
  <div class="container">
    <div class="columns">
      <div class="column is-offset-4 is-4">
        <h1 class="title">Record audio</h1>
        <article class="message is-success" id="alert">
          <div class="message-header">
            <p>Recorded successfully!</p>
            <button class="delete" aria-label="delete"></button>
          </div>
          <div class="message-body">
            You have successfully recorded your message. You can now click on
            the Submit button to post it.
          </div>
        </article>
        <form method="POST" enctype="multipart/form-data">
          {% csrf_token %}
          <div class="field">
            <div class="control has-icons-left has-icons-right">
              <input class="input" type="text" placeholder="Language" />
              <span class="icon is-left">
                <i class="fas fa-language"></i>
              </span>
              <span class="icon is-right">
                <i class="fas fa-check"></i>
              </span>
            </div>
            <div class="control has-icons-left has-icons-right">
              <audio id="recordAudio" class="video-js vjs-default-skin"></audio>
            </div>
          </div>
        </form>
      </div>
    </div>
  </div>
</section>
{% endblock content %}

<!--additional js-->
{% block js %}
<script src="{% static 'assets/js/video.min.js' %}"></script>
<script src="{% static 'assets/js/RecordRTC.js' %}"></script>
<script src="{% static 'assets/js/adapter-latest.js' %}"></script>
<script src="{% static 'assets/js/wavesurfer.js' %}"></script>
<script src="{% static 'assets/js/wavesurfer.microphone.min.js' %}"></script>
<script src="{% static 'assets/js/videojs.wavesurfer.min.js' %}"></script>

<script src="{% static 'assets/js/videojs.record.min.js' %}"></script>
<script src="{% static 'assets/js/browser-workaround.js' %}"></script>

{% endblock js %}


Všechny tyto dodatečné soubory byly zahrnuty v oficiálním příkladu pouze zvuku videojs-record knihovna. Návštěva http://localhost:8000/record/ by měl vypadat takto:

Krok 8 – Přidání záznamu a ajax hovory

Abychom měli skutečný pocit z nahrávání, udělejme to skutečné – nahrávání!

Vytvořte nový .js soubor v js podadresář vašeho static adresář souborů. Říkám tomu real.recording.js . Vyplňte jej následujícím způsobem:

// First lets hide the message
document.getElementById("alert").style.display = "none";
// Next, declare the options that will passed into the recording constructor
const options = {
  controls: true,
  bigPlayButton: false,
  width: 600,
  height: 300,
  fluid: true, // this ensures that it's responsive
  plugins: {
    wavesurfer: {
      backend: "WebAudio",
      waveColor: "#f7fff7", // change the wave color here. Background color was set in the css above
      progressColor: "#ffe66d",
      displayMilliseconds: true,
      debug: true,
      cursorWidth: 1,
      hideScrollbar: true,
      plugins: [
        // enable microphone plugin
        WaveSurfer.microphone.create({
          bufferSize: 4096,
          numberOfInputChannels: 1,
          numberOfOutputChannels: 1,
          constraints: {
            video: false,
            audio: true,
          },
        }),
      ],
    },
    record: {
      audio: true, // only audio is turned on
      video: false, // you can turn this on as well if you prefer video recording.
      maxLength: 60, // how long do you want the recording?
      displayMilliseconds: true,
      debug: true,
    },
  },
};

// apply audio workarounds for certain browsers
applyAudioWorkaround();

// create player and pass the the audio id we created then
var player = videojs("recordAudio", options, function () {
  // print version information at startup
  var msg =
    "Using video.js " +
    videojs.VERSION +
    " with videojs-record " +
    videojs.getPluginVersion("record") +
    ", videojs-wavesurfer " +
    videojs.getPluginVersion("wavesurfer") +
    ", wavesurfer.js " +
    WaveSurfer.VERSION +
    " and recordrtc " +
    RecordRTC.version;
  videojs.log(msg);
});

// error handling
player.on("deviceError", function () {
  console.log("device error:", player.deviceErrorCode);
});

player.on("error", function (element, error) {
  console.error(error);
});

// user clicked the record button and started recording
player.on("startRecord", function () {
  console.log("started recording!");
});

// user completed recording and stream is available
player.on("finishRecord", function () {
  const audioFile = player.recordedData;

  console.log("finished recording: ", audioFile);

  $("#submit").prop("disabled", false);
  document.getElementById("alert").style.display = "block";
});

Vaše templates/core/record.html by nyní měla vypadat takto:

<!--inherits the layout-->
{% extends '_base.html' %}
<!--static-->
{% load static %}
<!--title-->
{% block title %}{{page_title}}{% endblock title %}

<!--additional css-->

{% block css %}
<link href="{% static 'assets/css/video-js.css' %}" rel="stylesheet" />
<link href="{% static 'assets/css/all.min.css' %}" rel="stylesheet" />
<link
  href="{% static 'assets/css/videojs.wavesurfer.min.css' %}"
  rel="stylesheet"
/>
<link href="{% static 'assets/css/videojs.record.css' %}" rel="stylesheet" />
<style>
  /* change player background color */
  #recordAudio {
    background-color: #3e8ed0;
  }
</style>
{% endblock css %}
<!--content-->
{% block content %}
<section class="section">
  <div class="container">
    <div class="columns">
      <div class="column is-offset-4 is-4">
        <h1 class="title">Record audio</h1>
        <article class="message is-success" id="alert">
          <div class="message-header">
            <p>Recorded successfully!</p>
            <button class="delete" aria-label="delete"></button>
          </div>
          <div class="message-body">
            You have successfully recorded your message. You can now click on
            the Submit button to post it.
          </div>
        </article>
        <form method="POST" enctype="multipart/form-data">
          {% csrf_token %}
          <div class="field">
            <div class="control has-icons-left has-icons-right">
              <input class="input" type="text" placeholder="Language" />
              <span class="icon is-left">
                <i class="fas fa-language"></i>
              </span>
              <span class="icon is-right">
                <i class="fas fa-check"></i>
              </span>
            </div>
            <div
              class="control has-icons-left has-icons-right"
              style="margin-top: 1rem"
            >
              <audio id="recordAudio" class="video-js vjs-default-skin"></audio>
            </div>
            <div class="control" style="margin-top: 1rem">
              <button class="button is-info" id="submit">Submit</button>
            </div>
          </div>
        </form>
      </div>
    </div>
  </div>
</section>
{% endblock content %}

<!--additional js-->
{% block js %}
<script src="{% static 'assets/js/video.min.js' %}"></script>
<script src="{% static 'assets/js/RecordRTC.js' %}"></script>
<script src="{% static 'assets/js/adapter-latest.js' %}"></script>
<script src="{% static 'assets/js/wavesurfer.js' %}"></script>
<script src="{% static 'assets/js/wavesurfer.microphone.min.js' %}"></script>
<script src="{% static 'assets/js/videojs.wavesurfer.min.js' %}"></script>

<script src="{% static 'assets/js/videojs.record.min.js' %}"></script>
<script src="{% static 'assets/js/browser-workaround.js' %}"></script>
<script src="{% static 'assets/js/real.recording.js' %}"></script>
{% endblock js %}

Správný Ajax:

...

// Give event listener to the submit button
$("#submit").on("click", function (event) {
  event.preventDefault();
  let btn = $(this);
  //   change the button text and disable it
  btn.html("Submitting...").prop("disabled", true).addClass("disable-btn");
  //   create a new File with the recordedData and its name
  const recordedFile = new File([player.recordedData], `audiorecord.webm`);
  //   grabs the value of the language field
  const language = document.getElementById("language").value;
  //   initializes an empty FormData
  let data = new FormData();
  //   appends the recorded file and language value
  data.append("recorded_audio", recordedFile);
  data.append("language", language);
  //   post url endpoint
  const url = "";
  $.ajax({
    url: url,
    method: "POST",
    data: data,
    dataType: "json",
    success: function (response) {
      if (response.success) {
        document.getElementById("alert").style.display = "block";
        window.location.href = "/";
      } else {
        btn.html("Error").prop("disabled", false);
      }
    },
    error: function (error) {
      console.error(error);
    },
    cache: false,
    processData: false,
    contentType: false,
  });
});

Malá aktualizace

ajax kód může selhat nebo poskytnout nežádoucí výstup v prohlížečích Firefox, pokud je event argument není předán ve funkci zpětného volání, za nímž následuje první řádek event.preventDefault(); .

A je to! Takový dlouhý kousek. Máte nějaké návrhy? Zanechte je laskavě v sekci komentářů.