diff --git a/configure b/configure
index 32fc26356c..81be23a54f 100755
--- a/configure
+++ b/configure
@@ -301,6 +301,8 @@ External library support:
   --enable-mbedtls         enable mbedTLS, needed for https support
                            if openssl, gnutls or libtls is not used [no]
   --enable-mediacodec      enable Android MediaCodec support [no]
+  --disable-nwf            disable Apple Network.framework, needed for TLS support
+                           on macOS if openssl and gnutls are not used [autodetect]
   --enable-libmysofa       enable libmysofa, needed for sofalizer filter [no]
   --enable-openal          enable OpenAL 1.1 capture support [no]
   --enable-opencl          enable OpenCL processing [no]
@@ -1693,6 +1695,7 @@ EXTERNAL_AUTODETECT_LIBRARY_LIST="
     libxcb_shape
     libxcb_xfixes
     lzma
+    nwf
     schannel
     sdl2
     securetransport
@@ -3357,6 +3360,7 @@ https_protocol_suggest="zlib"
 icecast_protocol_select="http_protocol"
 mmsh_protocol_select="http_protocol"
 mmst_protocol_select="network"
+nwf_conflict="openssl gnutls libtls mbedtls"
 rtmp_protocol_conflict="librtmp_protocol"
 rtmp_protocol_select="tcp_protocol"
 rtmp_protocol_suggest="zlib"
@@ -3378,7 +3382,7 @@ sctp_protocol_select="network"
 securetransport_conflict="openssl gnutls libtls mbedtls"
 srtp_protocol_select="rtp_protocol srtp"
 tcp_protocol_select="network"
-tls_protocol_deps_any="gnutls openssl schannel securetransport libtls mbedtls"
+tls_protocol_deps_any="gnutls nwf openssl schannel securetransport libtls mbedtls"
 tls_protocol_select="tcp_protocol"
 udp_protocol_select="network"
 udplite_protocol_select="network"
@@ -6353,8 +6357,11 @@ if enabled decklink; then
     esac
 fi
 
+enabled nwf &&
+    check_lib nwf "Network/Network.h" "nw_connection_create" "-Wl,-framework,CoreFoundation -Wl,-framework,Security -Wl,-weak_framework,Network" ||
+        disable nwf
+
 enabled securetransport &&
-    check_func SecIdentityCreate "-Wl,-framework,CoreFoundation -Wl,-framework,Security" &&
     check_lib securetransport "Security/SecureTransport.h Security/Security.h" "SSLCreateContext" "-Wl,-framework,CoreFoundation -Wl,-framework,Security" ||
         disable securetransport
 
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 358d4abf49..fdf981bedc 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -613,6 +613,7 @@ OBJS-$(CONFIG_TCP_PROTOCOL)              += tcp.o
 TLS-OBJS-$(CONFIG_GNUTLS)                += tls_gnutls.o
 TLS-OBJS-$(CONFIG_LIBTLS)                += tls_libtls.o
 TLS-OBJS-$(CONFIG_MBEDTLS)               += tls_mbedtls.o
+TLS-OBJS-$(CONFIG_NWF)                   += tls_apple.o
 TLS-OBJS-$(CONFIG_OPENSSL)               += tls_openssl.o
 TLS-OBJS-$(CONFIG_SECURETRANSPORT)       += tls_apple.o
 TLS-OBJS-$(CONFIG_SCHANNEL)              += tls_schannel.o
diff --git a/libavformat/tls_apple.c b/libavformat/tls_apple.c
index 23042eb8ee..dd202eb9c0 100644
--- a/libavformat/tls_apple.c
+++ b/libavformat/tls_apple.c
@@ -19,7 +19,7 @@
  */
 
 #include <errno.h>
-
+#include <pthread.h>
 
 #include "avformat.h"
 #include "avio_internal.h"
@@ -37,18 +37,88 @@
 #include <Security/SecureTransport.h>
 #include <CoreFoundation/CoreFoundation.h>
 
+#if CONFIG_NWF
+#include <Network/Network.h>
+#endif
+
 // We use a private API call here; it's good enough for WebKit.
 SecIdentityRef __attribute__((weak)) SecIdentityCreate(CFAllocatorRef allocator, SecCertificateRef certificate, SecKeyRef privateKey);
 #define ioErr -36
 
+#define NWF_CHECK __builtin_available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *)
+
 typedef struct TLSContext {
     const AVClass *class;
     TLSShared tls_shared;
     SSLContextRef ssl_context;
     CFArrayRef ca_array;
     int lastErr;
+#if CONFIG_NWF
+    nw_connection_t nw_conn;
+    dispatch_semaphore_t semaphore;
+    dispatch_semaphore_t state_semaphore;
+    pthread_mutex_t state_lock;
+    int at_eof;
+    nw_listener_state_t nw_listen_state;
+    nw_connection_state_t nw_state;
+    nw_error_t nw_state_error;
+    int tcp_nodelay;
+#endif
 } TLSContext;
 
+#if CONFIG_NWF
+static int nwf_init = 0;
+static dispatch_queue_t nwf_queue = NULL;
+
+static int print_nwf_error(void *log_ctx, nw_error_t error, int successRet, const char *func)
+{
+    if (!error)
+        return successRet;
+
+    switch (nw_error_get_error_domain(error)) {
+    case nw_error_domain_posix:
+        return AVERROR(nw_error_get_error_code(error));
+    default:
+        av_log(log_ctx, AV_LOG_ERROR, "[%s] Error domain %u, code %i\n", func,
+               nw_error_get_error_domain(error),
+               nw_error_get_error_code(error));
+        return AVERROR(EIO);
+    }
+}
+
+#define PRINT_NWF_ERROR(err, successRet) print_nwf_error(h, err, successRet, __FUNCTION__)
+
+static int ff_nwf_init(void)
+{
+    if (NWF_CHECK) {
+        int ret = 0;
+        ff_lock_avformat();
+        if (!nwf_init)
+            nwf_queue = dispatch_queue_create("org.ffmpeg.nwf", DISPATCH_QUEUE_SERIAL);
+        if (nwf_queue)
+            nwf_init++;
+        else
+            ret = AVERROR(ENOMEM);
+        ff_unlock_avformat();
+
+        return ret;
+    }
+
+    return AVERROR(EINVAL);
+}
+
+static void ff_nwf_deinit(void)
+{
+    if (NWF_CHECK) {
+        ff_lock_avformat();
+        nwf_init--;
+        if (!nwf_init)
+            dispatch_release(nwf_queue);
+        ff_unlock_avformat();
+    }
+}
+#endif
+
 static int print_tls_error(URLContext *h, int ret)
 {
     TLSContext *c = h->priv_data;
@@ -272,6 +342,35 @@ static OSStatus tls_write_cb(SSLConnectionRef connection, const void *data, size
 static int tls_close(URLContext *h)
 {
     TLSContext *c = h->priv_data;
+
+#if CONFIG_NWF
+    if (NWF_CHECK) {
+        if (c->nw_conn) {
+            nw_connection_cancel(c->nw_conn);
+            nw_release(c->nw_conn);
+        }
+
+        pthread_mutex_lock(&c->state_lock);
+        while (c->nw_state != nw_connection_state_cancelled &&
+               c->nw_state != nw_connection_state_failed &&
+               c->nw_state != nw_connection_state_invalid) {
+            pthread_mutex_unlock(&c->state_lock);
+            dispatch_semaphore_wait(c->state_semaphore, DISPATCH_TIME_FOREVER);
+            pthread_mutex_lock(&c->state_lock);
+        }
+        pthread_mutex_unlock(&c->state_lock);
+
+        if (c->semaphore)
+            dispatch_release(c->semaphore);
+        if (c->state_semaphore)
+            dispatch_release(c->state_semaphore);
+
+        pthread_mutex_destroy(&c->state_lock);
+
+        ff_nwf_deinit();
+    }
+#endif
+
     if (c->ssl_context) {
         SSLClose(c->ssl_context);
         CFRelease(c->ssl_context);
@@ -296,7 +395,235 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op
 {
     TLSContext *c = h->priv_data;
     TLSShared *s = &c->tls_shared;
-    int ret;
+    int ret = 0;
+
+    if (s->ca_file) {
+        if ((ret = load_ca(h)) < 0)
+            goto fail;
+    }
+
+#if CONFIG_NWF
+    if (NWF_CHECK) {
+        int port;
+        char portStr[8];
+        SecIdentityRef identity = NULL;
+        CFArrayRef certArray = NULL;
+        nw_endpoint_t endpoint = NULL;
+        nw_parameters_t parameters = NULL;
+        bool finished = false;
+
+        if ((ret = ff_nwf_init()) < 0)
+            return ret;
+
+        if ((ret = AVERROR(pthread_mutex_init(&c->state_lock, NULL))) < 0)
+            goto nwf_fail;
+
+        if ((ret = ff_tls_process_underlying(s, h, uri, &port)) < 0)
+            goto nwf_fail;
+
+        snprintf(portStr, sizeof(portStr), "%i", port);
+
+        if (!(c->semaphore = dispatch_semaphore_create(0))) {
+            ret = AVERROR(ENOMEM);
+            goto nwf_fail;
+        }
+
+        if (!(c->state_semaphore = dispatch_semaphore_create(0))) {
+            ret = AVERROR(ENOMEM);
+            goto nwf_fail;
+        }
+
+        if (!(endpoint = nw_endpoint_create_host(s->underlying_host, portStr))) {
+            ret = AVERROR(ENOMEM);
+            goto nwf_fail;
+        }
+
+        if (s->cert_file && (ret = load_identity(h, &identity, &certArray)) < 0)
+            goto nwf_fail;
+
+        if (!(parameters = nw_parameters_create_secure_tcp(^(nw_protocol_options_t tls_options) {
+            sec_protocol_options_t options = nw_tls_copy_sec_protocol_options(tls_options);
+            sec_protocol_options_set_tls_server_name(options, s->host);
+            sec_protocol_options_set_peer_authentication_required(options, s->verify);
+            if (s->ca_file || !s->verify)
+                sec_protocol_options_set_verify_block(options, ^(sec_protocol_metadata_t metadata, sec_trust_t trust_ref, sec_protocol_verify_complete_t complete) {
+                    bool succ = false;
+                    SecTrustRef peerTrust = NULL;
+                    SecTrustResultType trustResult;
+
+                    // set_peer_authentication_required is buggy; seems to no-op
+                    if (!s->verify)
+                        complete(true);
+
+                    if (!(peerTrust = sec_trust_copy_ref(trust_ref)))
+                        goto verify_fail;
+
+                    if (SecTrustSetAnchorCertificates(peerTrust, c->ca_array) != noErr)
+                        goto verify_fail;
+
+                    if (SecTrustEvaluate(peerTrust, &trustResult) != noErr)
+                        goto verify_fail;
+
+                    succ = (trustResult == kSecTrustResultProceed ||
+                            trustResult == kSecTrustResultUnspecified);
+
+verify_fail:
+                    if (peerTrust)
+                        CFRelease(peerTrust);
+                    complete(succ);
+                }, nwf_queue);
+            if (identity) {
+                sec_identity_t sec_id = sec_identity_create_with_certificates(identity, certArray);
+                if (sec_id) {
+                    sec_protocol_options_set_local_identity(options, sec_id);
+                    nw_release(sec_id);
+                }
+            }
+        }, ^(nw_protocol_options_t tcp_options) {
+            nw_tcp_options_set_no_delay(tcp_options, c->tcp_nodelay);
+        }))) {
+            ret = AVERROR(ENOMEM);
+            goto nwf_fail;
+        }
+
+        if (s->listen) {
+            nw_listener_t listener;
+
+            nw_parameters_set_local_endpoint(parameters, endpoint);
+
+            listener = nw_listener_create(parameters);
+            if (!listener) {
+                ret = AVERROR(ENOMEM);
+                goto nwf_fail;
+            }
+
+            nw_listener_set_queue(listener, nwf_queue);
+
+            nw_listener_set_new_connection_handler(listener, ^(nw_connection_t connection) {
+                pthread_mutex_lock(&c->state_lock);
+                if (!c->nw_conn) {
+                    nw_retain(connection);
+                    c->nw_conn = connection;
+                    dispatch_semaphore_signal(c->state_semaphore);
+                }
+                pthread_mutex_unlock(&c->state_lock);
+            });
+            nw_listener_set_state_changed_handler(listener, ^(nw_listener_state_t state, nw_error_t error) {
+                pthread_mutex_lock(&c->state_lock);
+                c->nw_listen_state = state;
+                c->nw_state_error = error;
+                dispatch_semaphore_signal(c->state_semaphore);
+                pthread_mutex_unlock(&c->state_lock);
+            });
+
+            nw_listener_start(listener);
+
+            while (!finished) {
+                dispatch_semaphore_wait(c->state_semaphore, DISPATCH_TIME_FOREVER);
+                pthread_mutex_lock(&c->state_lock);
+                switch (c->nw_listen_state) {
+                case nw_listener_state_invalid:
+                    ret = AVERROR_UNKNOWN;
+                    finished = true;
+                    break;
+                case nw_listener_state_waiting:
+                    break;
+                case nw_listener_state_ready:
+                    ret = 0;
+                    if (c->nw_conn)
+                        finished = true;
+                    break;
+                case nw_listener_state_failed:
+                default:
+                    ret = PRINT_NWF_ERROR(c->nw_state_error, AVERROR_UNKNOWN);
+                    finished = true;
+                    break;
+                case nw_listener_state_cancelled:
+                    ret = AVERROR(EIO);
+                    finished = true;
+                    break;
+                }
+                pthread_mutex_unlock(&c->state_lock);
+            }
+
+            finished = false;
+
+            nw_listener_cancel(listener);
+            nw_release(listener);
+
+            pthread_mutex_lock(&c->state_lock);
+            while (c->nw_listen_state != nw_listener_state_cancelled &&
+                   c->nw_listen_state != nw_listener_state_failed &&
+                   c->nw_listen_state != nw_listener_state_invalid) {
+                pthread_mutex_unlock(&c->state_lock);
+                dispatch_semaphore_wait(c->state_semaphore, DISPATCH_TIME_FOREVER);
+                pthread_mutex_lock(&c->state_lock);
+            }
+            pthread_mutex_unlock(&c->state_lock);
+
+            if (!c->nw_conn)
+                goto nwf_fail;
+        } else {
+            if (!(c->nw_conn = nw_connection_create(endpoint, parameters))) {
+                ret = AVERROR(ENOMEM);
+                goto nwf_fail;
+            }
+        }
+
+        nw_connection_set_state_changed_handler(c->nw_conn, ^(nw_connection_state_t state, nw_error_t error) {
+            pthread_mutex_lock(&c->state_lock);
+            c->nw_state = state;
+            c->nw_state_error = error;
+            dispatch_semaphore_signal(c->state_semaphore);
+            pthread_mutex_unlock(&c->state_lock);
+        });
+
+        nw_connection_set_queue(c->nw_conn, nwf_queue);
+
+        nw_connection_start(c->nw_conn);
+
+        while (!finished) {
+            dispatch_semaphore_wait(c->state_semaphore, DISPATCH_TIME_FOREVER);
+            pthread_mutex_lock(&c->state_lock);
+            switch (c->nw_state) {
+            case nw_connection_state_invalid:
+                ret = AVERROR_UNKNOWN;
+                finished = true;
+                break;
+            case nw_connection_state_waiting:
+            case nw_connection_state_preparing:
+                break;
+            case nw_connection_state_ready:
+                ret = 0;
+                finished = true;
+                break;
+            case nw_connection_state_failed:
+            default:
+                ret = PRINT_NWF_ERROR(c->nw_state_error, AVERROR_UNKNOWN);
+                finished = true;
+                break;
+            case nw_connection_state_cancelled:
+                ret = AVERROR(EIO);
+                finished = true;
+                break;
+            }
+            pthread_mutex_unlock(&c->state_lock);
+        }
+
+nwf_fail:
+        if (endpoint)
+            nw_release(endpoint);
+        if (parameters)
+            nw_release(parameters);
+        if (certArray)
+            CFRelease(certArray);
+        if (identity)
+            CFRelease(identity);
+        if (ret < 0)
+            tls_close(h);
+        return ret;
+    }
+#endif
 
     if ((ret = ff_tls_open_underlying(s, h, uri, options)) < 0)
         goto fail;
@@ -307,10 +634,6 @@ static int tls_open(URLContext *h, const char *uri, int flags, AVDictionary **op
         ret = AVERROR(ENOMEM);
         goto fail;
     }
-    if (s->ca_file) {
-        if ((ret = load_ca(h)) < 0)
-            goto fail;
-    }
     if (s->ca_file || !s->verify)
         CHECK_ERROR(SSLSetSessionOption, c->ssl_context, kSSLSessionOptionBreakOnServerAuth, true);
     if (s->cert_file)
@@ -393,6 +716,41 @@ static int tls_read(URLContext *h, uint8_t *buf, int size)
     TLSContext *c = h->priv_data;
     size_t available = 0, processed = 0;
     int ret;
+
+#if CONFIG_NWF
+    if (NWF_CHECK) {
+        __block nw_error_t error;
+        __block int gotSize = 0;
+
+        if (c->at_eof)
+            return AVERROR_EOF;
+
+        nw_connection_receive(c->nw_conn, 1, size, ^(dispatch_data_t content, nw_content_context_t context, bool is_complete, nw_error_t inError) {
+            if (is_complete)
+                c->at_eof = 1;
+
+            if (content) {
+                gotSize = dispatch_data_get_size(content);
+
+                dispatch_data_apply(content, ^(dispatch_data_t region, size_t offset, const void *buffer, size_t inSize) {
+                    memcpy(buf + offset, buffer, inSize);
+                    return (bool)true;
+                });
+            }
+
+            error = inError;
+            dispatch_semaphore_signal(c->semaphore);
+        });
+
+        dispatch_semaphore_wait(c->semaphore, DISPATCH_TIME_FOREVER);
+
+        if (c->at_eof && !gotSize && !error)
+            return AVERROR_EOF;
+
+        return PRINT_NWF_ERROR(error, gotSize);
+    }
+#endif
+
     SSLGetBufferedReadSize(c->ssl_context, &available);
     if (available)
         size = FFMIN(available, size);
@@ -409,7 +767,25 @@ static int tls_write(URLContext *h, const uint8_t *buf, int size)
 {
     TLSContext *c = h->priv_data;
     size_t processed = 0;
-    int ret = SSLWrite(c->ssl_context, buf, size, &processed);
+    int ret;
+
+#if CONFIG_NWF
+    if (NWF_CHECK) {
+        __block nw_error_t error;
+        dispatch_data_t content = dispatch_data_create(buf, size, nwf_queue, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
+        nw_connection_send(c->nw_conn, content, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true, ^(nw_error_t inError) {
+            error = inError;
+            dispatch_semaphore_signal(c->semaphore);
+        });
+        dispatch_release(content);
+
+        dispatch_semaphore_wait(c->semaphore, DISPATCH_TIME_FOREVER);
+
+        return PRINT_NWF_ERROR(error, size);
+    }
+#endif
+
+    ret = SSLWrite(c->ssl_context, buf, size, &processed);
     ret = map_ssl_error(ret, processed);
     if (ret > 0)
         return ret;
@@ -421,11 +797,22 @@ static int tls_write(URLContext *h, const uint8_t *buf, int size)
 static int tls_get_file_handle(URLContext *h)
 {
     TLSContext *c = h->priv_data;
+
+#if CONFIG_NWF
+    if (NWF_CHECK) // Network.framework doesn't expose file handles
+        return -1;
+#endif
+
     return ffurl_get_file_handle(c->tls_shared.tcp);
 }
 
+#define OFFSET(v) offsetof(TLSContext, v)
+
 static const AVOption options[] = {
     TLS_COMMON_OPTIONS(TLSContext, tls_shared),
+#if CONFIG_NWF
+    { "tcp_nodelay", "Use TCP_NODELAY to disable nagle's algorithm", OFFSET(tcp_nodelay), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, .flags = TLS_OPTFL },
+#endif
     { NULL }
 };
 
