/* Copyright 2020 Jaakko Keränen Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "media.h" #include "gmdocument.h" #include #include "the_Foundation/block.h" #include "ui/window.h" #include "render/paint.h" /* size_SDLTexture */ #include "audio/player.h" #include "app.h" #include "stb_image.h" #include "stb_image_resize2.h" #include "jpegxl.h" // LAGRANGE_ENABLE_JXL #if defined (LAGRANGE_ENABLE_WEBP) # include #endif #include #include #include #include #include #include struct Impl_Media { iPtrArray items[max_MediaType]; /* TODO: Add a hash to quickly look up a link's media. */ #if defined (LAGRANGE_ENABLE_JXL) iJpegxl *jxl; #endif }; iDefineTypeConstruction(Media) /*----------------------------------------------------------------------------------------------*/ iDeclareType(GmMediaProps) struct Impl_GmMediaProps { iGmLinkId linkId; iString mime; iString url; iBool isPermanent; }; static void init_GmMediaProps_(iGmMediaProps *d) { d->linkId = 0; init_String(&d->mime); init_String(&d->url); d->isPermanent = iFalse; } static void deinit_GmMediaProps_(iGmMediaProps *d) { deinit_String(&d->url); deinit_String(&d->mime); } /*----------------------------------------------------------------------------------------------*/ iDeclareType(GmImage) struct Impl_GmImage { iGmMediaProps props; iBlock partialData; /* cleared when image is converted to texture */ iInt2 size; size_t numBytes; SDL_Texture *texture; iBool didFail; iMedia *media; /* backlink to owner */ }; void init_GmImage(iGmImage *d, const iBlock *data) { init_GmMediaProps_(&d->props); initCopy_Block(&d->partialData, data); d->size = zero_I2(); d->numBytes = 0; d->texture = NULL; d->didFail = iFalse; d->media = NULL; } void deinit_GmImage(iGmImage *d) { deinit_Block(&d->partialData); SDL_DestroyTexture(d->texture); deinit_GmMediaProps_(&d->props); } static void applyImageStyle_(enum iImageStyle style, iInt2 size, uint8_t *imgData) { if (style == original_ImageStyle) { return; } uint8_t *pos = imgData; size_t numPixels = size.x * size.y; float brighten = 0.0f; if (style == bgFg_ImageStyle) { iColor dark = get_Color(tmBackground_ColorId); iColor light = get_Color(tmParagraph_ColorId); if (hsl_Color(dark).lum > hsl_Color(light).lum) { iSwap(iColor, dark, light); } while (numPixels-- > 0) { iHSLColor hsl = hsl_Color((iColor){ pos[0], pos[1], pos[2], 255 }); const float s = 1.0f - hsl.lum; const float t = hsl.lum; pos[0] = dark.r * s + light.r * t; pos[1] = dark.g * s + light.g * t; pos[2] = dark.b * s + light.b * t; pos += 4; } return; } iColor colorize = (iColor){ 255, 255, 255, 255 }; if (style != grayscale_ImageStyle) { colorize = get_Color(style == textColorized_ImageStyle ? tmParagraph_ColorId : tmPreformatted_ColorId); /* Compensate for change in mid-tones. */ const int colMax = iMax(iMax(colorize.r, colorize.g), colorize.b); brighten = iClamp(1.0f - (colorize.r + colorize.g + colorize.b) / (colMax * 3), 0.0f, 0.5f); } iHSLColor hslColorize = hsl_Color(colorize); while (numPixels-- > 0) { iHSLColor hsl = hsl_Color((iColor){ pos[0], pos[1], pos[2], 255 }); iHSLColor out = { hslColorize.hue, hslColorize.sat, hsl.lum, 1.0f }; out.lum = powf(out.lum, 1.0f + brighten * 2); iColor outRgb = rgb_HSLColor(out); pos[0] = powf(outRgb.r / 255.0f, 1.0f - brighten * 0.75f) * 255; pos[1] = powf(outRgb.g / 255.0f, 1.0f - brighten * 0.75f) * 255; pos[2] = powf(outRgb.b / 255.0f, 1.0f - brighten * 0.75f) * 255; pos += 4; } } static iBool makeImageTexture_Media_(iMedia *media, iGmImage *d, iBool isPartial) { iBlock *data = &d->partialData; d->numBytes = size_Block(data); uint8_t *imgData = NULL; iBool isNew = iFalse; if (!isPartial && equalMediaType_String(&d->props.mime, "image/webp")) { #if defined (LAGRANGE_ENABLE_WEBP) imgData = WebPDecodeRGBA(constData_Block(data), size_Block(data), &d->size.x, &d->size.y); #endif } else if (equalMediaType_String(&d->props.mime, "image/jxl")) { #if defined (LAGRANGE_ENABLE_JXL) imgData = decodeImage_Jpegxl(media->jxl, d->props.linkId, data, isPartial, &d->size); #endif } else if (!isPartial) { imgData = stbi_load_from_memory( constData_Block(data), (int) size_Block(data), &d->size.x, &d->size.y, NULL, 4); if (!imgData) { d->didFail = iTrue; fprintf(stderr, "[media] image load failed: %s\n", stbi_failure_reason()); } } if (!imgData) { d->size = zero_I2(); d->texture = NULL; } else { applyImageStyle_(prefs_App()->imageStyle, d->size, imgData); /* TODO: Save some memory by checking if the alpha channel is actually in use. */ iWindow *window = get_Window(); iInt2 texSize = d->size; /* Resize down to min(maximum texture size, window size). */ { SDL_Rect dispRect; SDL_GetDisplayBounds(SDL_GetWindowDisplayIndex(window->win), &dispRect); const iInt2 maxSize = min_I2(isEqual_I2(maxTextureSize_Window(window), zero_I2()) ? texSize : maxTextureSize_Window(window), coord_Window(window, dispRect.w, dispRect.h)); iInt2 scaled = d->size; if (scaled.x > maxSize.x) { scaled.y = scaled.y * maxSize.x / scaled.x; scaled.x = maxSize.x; } if (scaled.y > maxSize.y) { scaled.x = scaled.x * maxSize.y / scaled.y; scaled.y = maxSize.y; } if (!isEqual_I2(scaled, d->size)) { uint8_t *scaledImgData = malloc(scaled.x * scaled.y * 4); stbir_resize_uint8_linear(imgData, d->size.x, d->size.y, 4 * d->size.x, scaledImgData, scaled.x, scaled.y, scaled.x * 4, STBIR_RGBA); free(imgData); imgData = scaledImgData; texSize = scaled; /* We keep d->size for the UI. */ } } /* Create the texture. */ SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormatFrom( imgData, texSize.x, texSize.y, 32, texSize.x * 4, SDL_PIXELFORMAT_ABGR8888); /* TODO: In multiwindow case, all windows must have the same shared renderer? Or at least a shared context. */ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); /* linear scaling */ d->texture = SDL_CreateTextureFromSurface(renderer_Window(window), surface); SDL_FreeSurface(surface); free(imgData); isNew = iTrue; } if (!isPartial) { clear_Block(data); } return isNew; } iDefineTypeConstructionArgs(GmImage, (const iBlock *data), data) /*----------------------------------------------------------------------------------------------*/ iDeclareType(GmAudio) struct Impl_GmAudio { iGmMediaProps props; iPlayer *player; }; void init_GmAudio(iGmAudio *d) { init_GmMediaProps_(&d->props); #if defined (LAGRANGE_ENABLE_AUDIO) d->player = new_Player(); #endif } void deinit_GmAudio(iGmAudio *d) { #if defined (LAGRANGE_ENABLE_AUDIO) //delete_Player(d->player); /* On Android, the Java side may have just sent us a notification about this so give some time to react to the queued message. */ iCollectDel(d->player, delete_Player); #endif deinit_GmMediaProps_(&d->props); } iDefineTypeConstruction(GmAudio) /*----------------------------------------------------------------------------------------------*/ iDeclareType(GmDownload) struct Impl_GmDownload { iGmMediaProps props; uint64_t numBytes; iTime startTime; uint32_t rateStartTime; size_t rateNumBytes; float currentRate; iString * path; iFile * file; }; static iBool openFile_GmDownload_(iGmDownload *d) { iAssert(!isEmpty_String(&d->props.url)); d->path = copy_String(downloadPathForUrl_App(&d->props.url, &d->props.mime)); d->file = new_File(d->path); if (!open_File(d->file, writeOnly_FileMode)) { return iFalse; } return iTrue; } static void closeFile_GmDownload_(iGmDownload *d) { d->currentRate = (float) (d->numBytes / elapsedSeconds_Time(&d->startTime)); iReleasePtr(&d->file); } void init_GmDownload(iGmDownload *d) { init_GmMediaProps_(&d->props); initCurrent_Time(&d->startTime); d->numBytes = 0; d->rateStartTime = SDL_GetTicks(); d->rateNumBytes = 0; d->currentRate = 0.0f; d->path = NULL; d->file = NULL; } void deinit_GmDownload(iGmDownload *d) { closeFile_GmDownload_(d); deinit_GmMediaProps_(&d->props); delete_String(d->path); } static void writeToFile_GmDownload_(iGmDownload *d, const iBlock *data) { const static unsigned rateInterval_ = 1000; iAssert(d->file); writeData_File(d->file, constBegin_Block(data) + d->numBytes, size_Block(data) - d->numBytes); const size_t newBytes = size_Block(data) - d->numBytes; d->numBytes = size_Block(data); d->rateNumBytes += newBytes; const uint32_t now = SDL_GetTicks(); if (now - d->rateStartTime > rateInterval_) { const double elapsed = (double) (now - d->rateStartTime) / 1000.0; d->rateStartTime = now; d->currentRate = (float) (d->rateNumBytes / elapsed); d->rateNumBytes = 0; } } iDefineTypeConstruction(GmDownload) /*----------------------------------------------------------------------------------------------*/ void init_Media(iMedia *d) { iForIndices(i, d->items) { init_PtrArray(&d->items[i]); } #if defined (LAGRANGE_ENABLE_JXL) d->jxl = new_Jpegxl(); #endif } void deinit_Media(iMedia *d) { clear_Media(d); iForIndices(i, d->items) { deinit_PtrArray(&d->items[i]); } #if defined (LAGRANGE_ENABLE_JXL) delete_Jpegxl(d->jxl); #endif } void clear_Media(iMedia *d) { iForEach(PtrArray, i, &d->items[image_MediaType]) { deinit_GmImage(i.ptr); } iForEach(PtrArray, a, &d->items[audio_MediaType]) { deinit_GmAudio(a.ptr); } iForEach(PtrArray, n, &d->items[download_MediaType]) { deinit_GmDownload(n.ptr); } iForIndices(type, d->items) { clear_PtrArray(&d->items[type]); } #if defined (LAGRANGE_ENABLE_JXL) clear_Jpegxl(d->jxl); #endif } size_t memorySize_Media(const iMedia *d) { size_t memSize = 0; iConstForEach(PtrArray, i, &d->items[image_MediaType]) { const iGmImage *img = i.ptr; if (img->texture) { const iInt2 texSize = size_SDLTexture(img->texture); memSize += 4 * texSize.x * texSize.y; /* RGBA */ } else { memSize += size_Block(&img->partialData); } } #if defined (LAGRANGE_ENABLE_AUDIO) iConstForEach(PtrArray, a, &d->items[audio_MediaType]) { const iGmAudio *audio = a.ptr; if (audio->player) { memSize += sourceDataSize_Player(audio->player); } } #endif iConstForEach(PtrArray, n, &d->items[download_MediaType]) { const iGmDownload *down = n.ptr; memSize += down->numBytes; } return memSize; } iBool setUrl_Media(iMedia *d, iGmLinkId linkId, enum iMediaType mediaType, const iString *url) { iMediaId existing = findMediaForLink_Media(d, linkId, mediaType); const iBool isNew = !existing.id; iGmMediaProps *props = NULL; if (mediaType == download_MediaType) { iGmDownload *dl = NULL; if (isNew) { dl = new_GmDownload(); pushBack_PtrArray(&d->items[download_MediaType], dl); } else { dl = at_PtrArray(&d->items[download_MediaType], index_MediaId(existing)); } props = &dl->props; } if (props) { props->linkId = linkId; props->isPermanent = iTrue; set_String(&props->url, url); } return isNew; } iBool setData_Media(iMedia *d, iGmLinkId linkId, const iString *mime, const iBlock *data, int flags) { const iBool isPartial = (flags & partialData_MediaFlag) != 0; const iBool allowHide = (flags & allowHide_MediaFlag) != 0; const iBool isDeleting = (!mime || !data); iMediaId existing = findMediaForLink_Media(d, linkId, none_MediaType); const size_t existingIndex = index_MediaId(existing); iBool isNew = iFalse; if (existing.type == image_MediaType) { iGmImage *img; if (isDeleting) { take_PtrArray(&d->items[image_MediaType], existingIndex, (void **) &img); #if defined (LAGRANGE_ENABLE_JXL) cancel_Jpegxl(d->jxl, linkId); #endif delete_GmImage(img); } else { img = at_PtrArray(&d->items[image_MediaType], existingIndex); iAssert(equal_String(&img->props.mime, mime)); /* MIME cannot change */ set_Block(&img->partialData, data); isNew = makeImageTexture_Media_(d, img, isPartial); } } else if (existing.type == audio_MediaType) { #if defined (LAGRANGE_ENABLE_AUDIO) iGmAudio *audio; if (isDeleting) { take_PtrArray(&d->items[audio_MediaType], existingIndex, (void **) &audio); delete_GmAudio(audio); } else { audio = at_PtrArray(&d->items[audio_MediaType], existingIndex); iAssert(equal_String(&audio->props.mime, mime)); /* MIME cannot change */ updateSourceData_Player(audio->player, mime, data, append_PlayerUpdate); if (!isPartial) { updateSourceData_Player(audio->player, NULL, NULL, complete_PlayerUpdate); } if (!isStarted_Player(audio->player)) { /* Maybe the previous updates didn't have enough data. */ if (start_Player(audio->player)) { postCommandf_App("media.player.started player:%p", audio->player); } } } #endif } else if (existing.type == download_MediaType) { iGmDownload *dl; if (isDeleting) { take_PtrArray(&d->items[download_MediaType], existingIndex, (void **) &dl); delete_GmDownload(dl); } else { dl = at_PtrArray(&d->items[download_MediaType], existingIndex); if (isEmpty_String(&dl->props.mime)) { set_String(&dl->props.mime, mime); } if (!dl->file) { openFile_GmDownload_(dl); } writeToFile_GmDownload_(dl, data); if (!isPartial) { closeFile_GmDownload_(dl); } } } else if (!isDeleting) { if (startsWith_String(mime, "image/")) { /* Copy the image to a texture. */ iGmImage *img = new_GmImage(data); img->media = d; img->props.linkId = linkId; /* TODO: use a hash? */ img->props.isPermanent = !allowHide; set_String(&img->props.mime, mime); pushBack_PtrArray(&d->items[image_MediaType], img); isNew = makeImageTexture_Media_(d, img, isPartial); } else if (startsWith_String(mime, "audio/")) { #if defined (LAGRANGE_ENABLE_AUDIO) iGmAudio *audio = new_GmAudio(); audio->props.linkId = linkId; /* TODO: use a hash? */ audio->props.isPermanent = !allowHide; set_String(&audio->props.mime, mime); updateSourceData_Player(audio->player, mime, data, replace_PlayerUpdate); if (!isPartial) { updateSourceData_Player(audio->player, NULL, NULL, complete_PlayerUpdate); } pushBack_PtrArray(&d->items[audio_MediaType], audio); /* Start playing right away. */ if (start_Player(audio->player)) { postCommandf_App("media.player.started player:%p", audio->player); } isNew = iTrue; #endif /* LAGRANGE_ENABLE_AUDIO */ } } return isNew; } static iMediaId findMediaPtr_Media_(const iPtrArray *items, enum iMediaType mediaType, iGmLinkId linkId) { iConstForEach(PtrArray, i, items) { const iGmMediaProps *props = i.ptr; if (props->linkId == linkId) { return (iMediaId){ .type = mediaType, .id = index_PtrArrayConstIterator(&i) + 1 }; } } return iInvalidMediaId; } iMediaId findMediaForLink_Media(const iMedia *d, iGmLinkId linkId, enum iMediaType mediaType) { /* TODO: Use hashes, this will get very slow if there is a large number of media items. */ iMediaId mid = iInvalidMediaId; for (int i = 0; i < max_MediaType; i++) { if (mediaType == i || !mediaType) { mid = findMediaPtr_Media_(&d->items[i], i, linkId); if (mid.type) { break; } } } return mid; } size_t numAudio_Media(const iMedia *d) { return size_PtrArray(&d->items[audio_MediaType]); } iInt2 imageSize_Media(const iMedia *d, iMediaId imageId) { iAssert(imageId.type == image_MediaType); const size_t index = index_MediaId(imageId); if (index < size_PtrArray(&d->items[image_MediaType])) { const iGmImage *img = constAt_PtrArray(&d->items[image_MediaType], index); return img->size; } return zero_I2(); } iBool imageFailed_Media(const iMedia *d, iMediaId imageId) { iAssert(imageId.type == image_MediaType); const size_t index = index_MediaId(imageId); if (index < size_PtrArray(&d->items[image_MediaType])) { const iGmImage *img = constAt_PtrArray(&d->items[image_MediaType], index); return img->didFail; } return iTrue; } SDL_Texture *imageTexture_Media(const iMedia *d, iMediaId imageId) { iAssert(imageId.type == image_MediaType); const size_t index = index_MediaId(imageId); if (index < size_PtrArray(&d->items[image_MediaType])) { const iGmImage *img = constAt_PtrArray(&d->items[image_MediaType], index); return img->texture; } return NULL; } iBool info_Media(const iMedia *d, iMediaId mediaId, iGmMediaInfo *info_out) { /* TODO: Use a hash. */ const size_t index = index_MediaId(mediaId); switch (mediaId.type) { case image_MediaType: if (index < size_PtrArray(&d->items[image_MediaType])) { const iGmImage *img = constAt_PtrArray(&d->items[image_MediaType], index); info_out->numBytes = img->numBytes; info_out->type = cstr_String(&img->props.mime); info_out->isPermanent = img->props.isPermanent; return iTrue; } break; case audio_MediaType: if (index < size_PtrArray(&d->items[audio_MediaType])) { const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], index); info_out->type = cstr_String(&audio->props.mime); info_out->isPermanent = audio->props.isPermanent; return iTrue; } break; case download_MediaType: if (index < size_PtrArray(&d->items[download_MediaType])) { const iGmDownload *dl = constAt_PtrArray(&d->items[download_MediaType], index); info_out->type = cstr_String(&dl->props.mime); info_out->isPermanent = dl->props.isPermanent; info_out->numBytes = dl->numBytes; return iTrue; } break; default: break; } iZap(*info_out); return iFalse; } iPlayer *audioData_Media(const iMedia *d, iMediaId audioId) { iAssert(audioId.type == audio_MediaType); const size_t index = index_MediaId(audioId); if (index < size_PtrArray(&d->items[audio_MediaType])) { const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], index); return audio->player; } return NULL; } iPlayer *audioPlayer_Media(const iMedia *d, iMediaId audioId) { iAssert(audioId.type == audio_MediaType); const size_t index = index_MediaId(audioId); if (index < size_PtrArray(&d->items[audio_MediaType])) { const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], index); return audio->player; } return NULL; } #if 0 void pauseAllPlayers_Media(const iMedia *d, iBool setPaused) { #if defined (LAGRANGE_ENABLE_AUDIO) for (size_t i = 0; i < size_PtrArray(&d->items[audio_MediaType]); ++i) { const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], i); if (audio->player) { setPaused_Player(audio->player, setPaused); } } #endif } #endif void releasePlayers_Media(const iMedia *d) { #if defined (LAGRANGE_ENABLE_AUDIO) for (size_t i = 0; i < size_PtrArray(&d->items[audio_MediaType]); ++i) { const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], i); if (audio->player) { stop_Player(audio->player); } } #endif } void stopFinishedPlayers_Media(const iMedia *d) { #if defined (LAGRANGE_ENABLE_AUDIO) for (size_t i = 0; i < size_PtrArray(&d->items[audio_MediaType]); ++i) { const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], i); if (audio->player && isStarted_Player(audio->player) && isFinished_Player(audio->player)) { stop_Player(audio->player); } } #endif } size_t numActivePlayers_Media(const iMedia *d) { size_t n = 0; #if defined (LAGRANGE_ENABLE_AUDIO) for (size_t i = 0; i < size_PtrArray(&d->items[audio_MediaType]); ++i) { const iGmAudio *audio = constAt_PtrArray(&d->items[audio_MediaType], i); if (audio->player && isStarted_Player(audio->player) && !isPaused_Player(audio->player)) { n++; } } #endif return n; } void updateStreamData_Media(iMedia *d, iGmLinkId linkId, const iBlock *fullData) { #if defined (LAGRANGE_ENABLE_AUDIO) const iMediaId mid = findLinkAudio_Media(d, linkId); if (mid.type == audio_MediaType) { iPlayer *player = audioPlayer_Media(d, mid); if (player) { updateSourceData_Player(player, NULL, fullData, fullData ? append_PlayerUpdate : complete_PlayerUpdate); } } #endif } void downloadStats_Media(const iMedia *d, iMediaId downloadId, const iString **path_out, float *bytesPerSecond_out, iBool *isFinished_out) { iAssert(downloadId.type == download_MediaType); *path_out = NULL; *bytesPerSecond_out = 0.0f; *isFinished_out = iFalse; const size_t index = index_MediaId(downloadId); if (index < size_PtrArray(&d->items[download_MediaType])) { const iGmDownload *dl = constAt_PtrArray(&d->items[download_MediaType], index); if (dl->path) { *path_out = dl->path; } *bytesPerSecond_out = dl->currentRate; *isFinished_out = (dl->path && !dl->file); } } /*----------------------------------------------------------------------------------------------*/ static void updated_MediaRequest_(iAnyObject *obj) { iMediaRequest *d = obj; if (d->media) { iGmResponse *resp = lockResponse_GmRequest(d->req); updateStreamData_Media(d->media, d->linkId, &resp->body); unlockResponse_GmRequest(d->req); } postCommandf_App("media.updated link:%u request:%p", d->linkId, d); } static void finished_MediaRequest_(iAnyObject *obj) { iMediaRequest *d = obj; if (d->media) { iGmResponse *resp = lockResponse_GmRequest(d->req); updateStreamData_Media(d->media, d->linkId, &resp->body); unlockResponse_GmRequest(d->req); updateStreamData_Media(d->media, d->linkId, NULL); /* mark complete */ } postCommandf_App("media.finished link:%u request:%p", d->linkId, d); } void init_MediaRequest(iMediaRequest *d, iDocumentWidget *doc, iMedia *media, unsigned int linkId, const iString *url, iBool enableFilters, const iGmIdentity *overrideDefaultIdentity) { d->doc = doc; d->media = media; d->linkId = linkId; d->req = new_GmRequest(certs_App()); setUrl_GmRequest(d->req, url); enableFilters_GmRequest(d->req, enableFilters); if (overrideDefaultIdentity) { setIdentity_GmRequest(d->req, overrideDefaultIdentity); } iConnect(GmRequest, d->req, updated, d, updated_MediaRequest_); iConnect(GmRequest, d->req, finished, d, finished_MediaRequest_); submit_GmRequest(d->req); } void deinit_MediaRequest(iMediaRequest *d) { iDisconnect(GmRequest, d->req, updated, d, updated_MediaRequest_); iDisconnect(GmRequest, d->req, finished, d, finished_MediaRequest_); iRelease(d->req); } void resubmitWithUrl_MediaRequest(iMediaRequest *d, const iString *url) { iAssert(d->req); iAssert(isFinished_GmRequest(d->req)); const iBool enableFilters = filtersEnabled_GmRequest(d->req); deinit_MediaRequest(d); /* release request, disconnect audiences */ d->req = new_GmRequest(certs_App()); setUrl_GmRequest(d->req, url); enableFilters_GmRequest(d->req, enableFilters); iConnect(GmRequest, d->req, updated, d, updated_MediaRequest_); iConnect(GmRequest, d->req, finished, d, finished_MediaRequest_); submit_GmRequest(d->req); } iMediaRequest *newReused_MediaRequest(iDocumentWidget *doc, unsigned int linkId, iGmRequest *request) { iMediaRequest *d = new_Object(&Class_MediaRequest); d->doc = doc; d->media = NULL; /* reused requests are image/redirect only, never audio */ d->linkId = linkId; d->req = request; /* takes ownership */ iConnect(GmRequest, d->req, updated, d, updated_MediaRequest_); iConnect(GmRequest, d->req, finished, d, finished_MediaRequest_); return d; } iDefineObjectConstructionArgs(MediaRequest, (iDocumentWidget *doc, iMedia *media, unsigned int linkId, const iString *url, iBool enableFilters, const iGmIdentity *overrideDefaultIdentity), doc, media, linkId, url, enableFilters, overrideDefaultIdentity) iDefineClass(MediaRequest)