www.prestigeflowers.co.uk
Open in
urlscan Pro
2606:4700:10::6816:3055
Public Scan
Submitted URL: https://www.hermes.cloudgamingservers.com/
Effective URL: https://www.prestigeflowers.co.uk/charity-flowers/majestic?kk=a4c6294-192fec8cf9e-1bdcd&sfeed&utm_source=kelkoouk&utm_medium=cpc&u...
Submission: On November 06 via automatic, source certstream-suspicious — Scanned from GB
Effective URL: https://www.prestigeflowers.co.uk/charity-flowers/majestic?kk=a4c6294-192fec8cf9e-1bdcd&sfeed&utm_source=kelkoouk&utm_medium=cpc&u...
Submission: On November 06 via automatic, source certstream-suspicious — Scanned from GB
Form analysis
4 forms found in the DOMPOST https://www.prestigeflowers.co.uk/search
<form id="search-form" class="search-form" method="post" action="https://www.prestigeflowers.co.uk/search">
<input type="hidden" name="csrf_token" value="MTczMDg1MTQyNlBadHpWVVd4NnpNQzE4b0hLdDE0OWhyV1V3Nkd2WlN5"> <i class="fa-solid fa-magnifying-glass"></i>
<label class="search-bar" id="instantsearch-box" for="search">
<input type="text" class="placeholder-input" placeholder="Search Prestige Flowers..." style="display: none;">
<input name="search_box" id="search" class="search-input" type="search" placeholder="Search Prestige Flowers..." aria-label="Search"><span hidden="">Loading...</span></label>
</form>
POST https://www.prestigeflowers.co.uk/cart
<form id="form_add_cart" action="https://www.prestigeflowers.co.uk/cart" method="post">
<!-- Addons -->
<div class="product-step-box step2 mandatory">
<div class="product-step">
<span class="stage">
<i class="fa-regular fa-gift"></i>
</span>
<span class="product-step-title"> Add a Little Extra? </span>
</div>
<div class="product-step-box extras-box show_checkbox">
<div class="step-content included-addons">
<div class="included-addon">
<div class="included-addon__img">
<div class="image-wrapper"><img class="" alt="Premium Feed"
src="https://images.prestigeflowers.co.uk/fetch/w_90,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/premium-flower-food.webp"> </div>
</div>
<div class="included-addon__desc">
<span class="included-addon__title">Premium Feed</span>
<span class="included-addon__price">FREE!</span>
<label class="checkmark-container" for="attr_addons_1337_Premium Feed">
<span style="font-size: 0px;">Premium Feed</span> <input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1337_Premium Feed');" toggle-id="attr_addons_1337_PremiumFeed" checked=""
readonly="" class="included-addon__checkbox" id="attr_addons_1337_Premium Feed" name="attr_addons_1337_Premium Feed">
<span class="checkmark"></span>
</label>
</div>
</div>
</div>
</div>
<div class="product-step-box extras-box show_checkbox">
<div class="step-content featured-addons">
<div class="featured-addon">
<div class="featured-addon__img">
<div class="image-wrapper"><img class="" alt="180g Pralines"
src="https://images.prestigeflowers.co.uk/fetch/w_90,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_(AddOn)_Chocolate-RedPraline.jpg"> </div>
</div>
<div class="featured-addon__desc">
<span class="featured-addon__title">Luxury Chocolates 180g</span>
<span class="featured-addon__price">£4.99</span>
<div class="tooltip" data-content="Delicious praline chocolates, perfect for any occasion, may contain nuts*">
<i class="fa-regular fa-circle-info"></i>
</div>
<label class="checkmark-container" for="attr_addons_1007_180g Pralines">
<span style="font-size: 0px;">Luxury Chocolates 180g</span> <input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1007_180g Pralines');"
toggle-id="attr_addons_1007_180gPralines" class="featured-addon__checkbox" id="attr_addons_1007_180g Pralines" name="attr_addons_1007_180g Pralines">
<span class="checkmark"></span>
</label>
</div>
</div>
<div class="featured-addon">
<div class="featured-addon__img">
<div class="image-wrapper"><img class="" alt="400g Pralines"
src="https://images.prestigeflowers.co.uk/fetch/w_90,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_(AddOn)_Chocolate-BlackPraline.jpg"> </div>
</div>
<div class="featured-addon__desc">
<span class="featured-addon__title">Deluxe Chocolates 400g</span>
<span class="featured-addon__price">£9.99</span>
<div class="tooltip" data-content="A box of delicious luxury praline chocolates, may contain nuts*">
<i class="fa-regular fa-circle-info"></i>
</div>
<label class="checkmark-container" for="attr_addons_1008_400g Pralines">
<span style="font-size: 0px;">Deluxe Chocolates 400g</span> <input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1008_400g Pralines');"
toggle-id="attr_addons_1008_400gPralines" class="featured-addon__checkbox" id="attr_addons_1008_400g Pralines" name="attr_addons_1008_400g Pralines">
<span class="checkmark"></span>
</label>
</div>
</div>
<div class="featured-addon">
<div class="featured-addon__img">
<div class="image-wrapper"><img class="" alt="Clear Vase"
src="https://images.prestigeflowers.co.uk/fetch/w_90,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/new-clear-vase.png"> </div>
</div>
<div class="featured-addon__desc">
<span class="featured-addon__title">Clear Vase</span>
<span class="featured-addon__price">£4.99</span>
<div class="tooltip" data-content="A clear and versatile glass vase perfect for this bouquet">
<i class="fa-regular fa-circle-info"></i>
</div>
<label class="checkmark-container" for="attr_addons_5_Clear Vase">
<span style="font-size: 0px;">Clear Vase</span> <input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_5_Clear Vase');" toggle-id="attr_addons_5_ClearVase"
class="featured-addon__checkbox" id="attr_addons_5_Clear Vase" name="attr_addons_5_Clear Vase">
<span class="checkmark"></span>
</label>
</div>
</div>
<div class="featured-addon">
<div class="featured-addon__img">
<div class="image-wrapper"><img class="" alt="PP2202"
src="https://images.prestigeflowers.co.uk/fetch/w_90,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/aft-tea-postal-ENJOY.jpg"> </div>
</div>
<div class="featured-addon__desc">
<span class="featured-addon__title">Afternoon Tea Hamper</span>
<span class="featured-addon__price">£16.99</span>
<div class="tooltip" data-content="Delicious treats hamper perfect for celebrating">
<i class="fa-regular fa-circle-info"></i>
</div>
<label class="checkmark-container" for="attr_addons_1049_PP2202">
<span style="font-size: 0px;">Afternoon Tea Hamper</span> <input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1049_PP2202');" toggle-id="attr_addons_1049_PP2202"
class="featured-addon__checkbox" id="attr_addons_1049_PP2202" name="attr_addons_1049_PP2202">
<span class="checkmark"></span>
</label>
</div>
</div>
<div class="featured-addon">
<div class="featured-addon__img">
<div class="image-wrapper"><img class="" alt="Guylian Chocolates"
src="https://images.prestigeflowers.co.uk/fetch/w_90,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_(AddOn)_Chocolate-guylian.jpg"> </div>
</div>
<div class="featured-addon__desc">
<span class="featured-addon__title">Guylian Chocolates</span>
<span class="featured-addon__price">£2.99</span>
<div class="tooltip" data-content="Delicious Guylian chocolates the perfect accompaniment with flower. Weighs 42g">
<i class="fa-regular fa-circle-info"></i>
</div>
<label class="checkmark-container" for="attr_addons_1058_Guylian Chocolates">
<span style="font-size: 0px;">Guylian Chocolates</span> <input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1058_Guylian Chocolates');"
toggle-id="attr_addons_1058_GuylianChocolates" class="featured-addon__checkbox" id="attr_addons_1058_Guylian Chocolates" name="attr_addons_1058_Guylian Chocolates">
<span class="checkmark"></span>
</label>
</div>
</div>
<div class="featured-addon">
<div class="featured-addon__img">
<div class="image-wrapper"><img class="" alt="Birthday Cake"
src="https://images.prestigeflowers.co.uk/fetch/w_90,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_(AddOn)_Gift-BirthdayCake.jpg"> </div>
</div>
<div class="featured-addon__desc">
<span class="featured-addon__title">Birthday Cake</span>
<span class="featured-addon__price">£4.99</span>
<div class="tooltip" data-content="A delicious hand made birthday cake by Valley Bakery.">
<i class="fa-regular fa-circle-info"></i>
</div>
<label class="checkmark-container" for="attr_addons_1055_Birthday Cake">
<span style="font-size: 0px;">Birthday Cake</span> <input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1055_Birthday Cake');" toggle-id="attr_addons_1055_BirthdayCake"
class="featured-addon__checkbox" id="attr_addons_1055_Birthday Cake" name="attr_addons_1055_Birthday Cake">
<span class="checkmark"></span>
</label>
</div>
</div>
<div class="featured-addon">
<div class="featured-addon__img">
<div class="image-wrapper"><img class="" alt="Cuddly Bear"
src="https://images.prestigeflowers.co.uk/fetch/w_90,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_(AddOn)_Bear.jpg"> </div>
</div>
<div class="featured-addon__desc">
<span class="featured-addon__title">Luxury Teddy Bear</span>
<span class="featured-addon__price">£9.99</span>
<div class="tooltip" data-content="Limited Edition handmade teddy ready for his new home!
">
<i class="fa-regular fa-circle-info"></i>
</div>
<label class="checkmark-container" for="attr_addons_1051_Cuddly Bear">
<span style="font-size: 0px;">Luxury Teddy Bear</span> <input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1051_Cuddly Bear');" toggle-id="attr_addons_1051_CuddlyBear"
class="featured-addon__checkbox" id="attr_addons_1051_Cuddly Bear" name="attr_addons_1051_Cuddly Bear">
<span class="checkmark"></span>
</label>
</div>
</div>
<div class="featured-addon">
<div class="featured-addon__img">
<div class="image-wrapper"><img class="" alt=" Premium Assorted Belgian Chocolates - Round Box"
src="https://images.prestigeflowers.co.uk/fetch/w_90,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_(AddOn)_Chocolate-LuxuryRound.jpg"> </div>
</div>
<div class="featured-addon__desc">
<span class="featured-addon__title">Premium Belgian Chocolates</span>
<span class="featured-addon__price">£19.99</span>
<div class="tooltip" data-content="Delicious hand-crafted Belgian chocolates, perfect for elevating any occasion, this product contains alcohol">
<i class="fa-regular fa-circle-info"></i>
</div>
<label class="checkmark-container" for="attr_addons_1072_ Premium Assorted Belgian Chocolates - Round Box">
<span style="font-size: 0px;">Premium Belgian Chocolates</span> <input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1072_ Premium Assorted Belgian Chocolates - Round Box');"
toggle-id="attr_addons_1072_PremiumAssortedBelgianChocolates-RoundBox" class="featured-addon__checkbox" id="attr_addons_1072_ Premium Assorted Belgian Chocolates - Round Box"
name="attr_addons_1072_ Premium Assorted Belgian Chocolates - Round Box">
<span class="checkmark"></span>
</label>
</div>
</div>
</div>
</div>
</div>
<style>
.product-step-box.extras-box:has(.included-addons .checkmark-container[for="attr_addons_1337_Premium Feed"]):not(:has(.included-addon+.included-addon)) {
display: none;
}
.included-addons .included-addon:has(.checkmark-container[for="attr_addons_1337_Premium Feed"]) {
display: none;
}
</style>
<div class="product-step-box extras-box show_checkbox">
<div class="step-content">
<div class="owl_addons_carousel owl-carousel owl-theme owl-loaded owl-drag">
<div class="owl-stage-outer">
<div class="owl-stage" style="transform: translate3d(0px, 0px, 0px); transition: all; width: 3640px;">
<div class="owl-item active" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1159_Just For You Add On Chocolate ">
<div class="tooltip" data-content="Delicious handcrafted Milk Chocolate 'Just For You' Valley Chocolate Bar (60g)">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_JustForYou-60gChocBar_AddOn(600px-300dpi).jpg"
alt="Just For You Add On Chocolate ">
</span>
<span class="add-description"> Just For You Chocolate Bar <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £4.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1159_Just For You Add On Chocolate ');" toggle-id="attr_addons_1159_JustForYouAddOnChocolate"
id="attr_addons_1159_Just For You Add On Chocolate " name="attr_addons_1159_Just For You Add On Chocolate ">
<span class="checkmark"></span>
</label></div>
<div class="owl-item active" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1121_Tea And Biscuits">
<div class="tooltip" data-content="Delicious flower shaped biscuits with Yorkshire Tea Bags">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/new tea and biscuits.jpg"
alt="Tea And Biscuits">
</span>
<span class="add-description"> Tea and Biscuits <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £3.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1121_Tea And Biscuits');" toggle-id="attr_addons_1121_TeaAndBiscuits" id="attr_addons_1121_Tea And Biscuits"
name="attr_addons_1121_Tea And Biscuits">
<span class="checkmark"></span>
</label></div>
<div class="owl-item active" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_61_Sparkling French Wine">
<div class="tooltip" data-content="An elegant and refined bottle of bubbly">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_(AddOn)_Drinks-Pigalle.jpg"
alt="Sparkling French Wine">
</span>
<span class="add-description"> Sparkling French Wine (75cl) <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £19.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_61_Sparkling French Wine');" toggle-id="attr_addons_61_SparklingFrenchWine" id="attr_addons_61_Sparkling French Wine"
name="attr_addons_61_Sparkling French Wine">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1113_Happy Birthday Postal">
<div class="tooltip" data-content="The ultimate Happy Birthday gift with cake, candles & treats!">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_(AddOn)_PostalHamper-Birthday-Birthday.jpg"
alt="Happy Birthday Postal">
</span>
<span class="add-description"> Happy Birthday Gift Box <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £18.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1113_Happy Birthday Postal');" toggle-id="attr_addons_1113_HappyBirthdayPostal"
id="attr_addons_1113_Happy Birthday Postal" name="attr_addons_1113_Happy Birthday Postal">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1158_Add On Happy Birthday Chocolate ">
<div class="tooltip" data-content="Delicious handcrafted Milk Chocolate 'Happy Birthday' Valley Chocolate Bar (60g)">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_HappyBirthday-60gChocBar_AddOn(600px-300dpi).jpg"
alt="Add On Happy Birthday Chocolate ">
</span>
<span class="add-description"> Birthday Chocolate Bar <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £4.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1158_Add On Happy Birthday Chocolate ');" toggle-id="attr_addons_1158_AddOnHappyBirthdayChocolate"
id="attr_addons_1158_Add On Happy Birthday Chocolate " name="attr_addons_1158_Add On Happy Birthday Chocolate ">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1135_Spring Yankee Candle">
<div class="tooltip" data-content="A beautiful Garden Blooms Yankee Candle, for all occasions!">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_(AddOn)_Candle-YankeeGardenBlooms.jpg"
alt="Spring Yankee Candle">
</span>
<span class="add-description"> Garden Blooms Candle <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £9.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1135_Spring Yankee Candle');" toggle-id="attr_addons_1135_SpringYankeeCandle" id="attr_addons_1135_Spring Yankee Candle"
name="attr_addons_1135_Spring Yankee Candle">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1116_Afternoon Tea Gardening Postal">
<div class="tooltip" data-content="A lovely unique gift for nature lovers, artisan cakes, tea & seasonal planting bulbs! ">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/postal addons_0009_PH-Sent-with-love.jpg"
alt="Afternoon Tea Gardening Postal">
</span>
<span class="add-description"> Afternoon Tea Gardening Gift <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £16.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1116_Afternoon Tea Gardening Postal');" toggle-id="attr_addons_1116_AfternoonTeaGardeningPostal"
id="attr_addons_1116_Afternoon Tea Gardening Postal" name="attr_addons_1116_Afternoon Tea Gardening Postal">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1076_Daffodil Bulbs">
<div class="tooltip" data-content="10 Daffodil Bulbs ready for planting!">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF--Daffodils-bulb-add-on (1).jpg"
alt="Daffodil Bulbs">
</span>
<span class="add-description"> Daffodil Bulbs <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £4.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1076_Daffodil Bulbs');" toggle-id="attr_addons_1076_DaffodilBulbs" id="attr_addons_1076_Daffodil Bulbs"
name="attr_addons_1076_Daffodil Bulbs">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1048_PP2208">
<div class="tooltip" data-content="Cheers to all occasions with this wine gift!">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/wine-postal-ENJOY.jpg" alt="PP2208">
</span>
<span class="add-description"> Wine Hamper <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £16.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1048_PP2208');" toggle-id="attr_addons_1048_PP2208" id="attr_addons_1048_PP2208" name="attr_addons_1048_PP2208">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1155_New Champagne 75cl Add On">
<div class="tooltip" data-content="Luxurious Baron De Villeboerg Champagne perfect for celebrating!">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_(AddOn)_Drinks-BaronVilleboerg.jpg"
alt="New Champagne 75cl Add On">
</span>
<span class="add-description"> Champagne 75cl <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £34.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1155_New Champagne 75cl Add On');" toggle-id="attr_addons_1155_NewChampagne75clAddOn"
id="attr_addons_1155_New Champagne 75cl Add On" name="attr_addons_1155_New Champagne 75cl Add On">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1157_GIN + TONIC CANDLE ADD ON">
<div class="tooltip" data-content="A beautiful Gin & Tonic scented candle, for all occasions!">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF--Gin-and-Tonic-candle-add-on.jpg"
alt="GIN + TONIC CANDLE ADD ON">
</span>
<span class="add-description"> Gin + Tonic Candle <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £5.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1157_GIN + TONIC CANDLE ADD ON');" toggle-id="attr_addons_1157_GIN+TONICCANDLEADDON"
id="attr_addons_1157_GIN + TONIC CANDLE ADD ON" name="attr_addons_1157_GIN + TONIC CANDLE ADD ON">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_13_White Wine 37.5cl">
<div class="tooltip" data-content="Smooth, aromatic and refreshing French white wine, perfect to say Cheers! ">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_(AddOn)_Drinks-WhiteWine37cl.jpg"
alt="White Wine 37.5cl">
</span>
<span class="add-description"> White Wine (37.5cl) <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £7.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_13_White Wine 37.5cl');" toggle-id="attr_addons_13_WhiteWine37.5cl" id="attr_addons_13_White Wine 37.5cl"
name="attr_addons_13_White Wine 37.5cl">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1039_Eco Clear Vase">
<div class="tooltip" data-content="New! Eco handmade vase made from recycled glass
">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/luxe-vase.jpg" alt="Eco Clear Vase">
</span>
<span class="add-description"> Eco Clear Vase <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £8.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1039_Eco Clear Vase');" toggle-id="attr_addons_1039_EcoClearVase" id="attr_addons_1039_Eco Clear Vase"
name="attr_addons_1039_Eco Clear Vase">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1089_Dark Chocolate 90g Valley Chocolate ">
<div class="tooltip" data-content="Delicious handcrafted Dark Chocolate and Raspberry Valley Chocolate Bar (90g)">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_(AddOn)_Chocolate-DarkRaspberryBar.jpg"
alt="Dark Chocolate 90g Valley Chocolate ">
</span>
<span class="add-description"> Dark Chocolate & Raspberry Bar <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £4.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1089_Dark Chocolate 90g Valley Chocolate ');" toggle-id="attr_addons_1089_DarkChocolate90gValleyChocolate"
id="attr_addons_1089_Dark Chocolate 90g Valley Chocolate " name="attr_addons_1089_Dark Chocolate 90g Valley Chocolate ">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1017_Birthday Pick">
<div class="tooltip" data-content="Add a delightful pick for extra surprise
">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/birthday_pick.png" alt="Birthday Pick">
</span>
<span class="add-description"> Happy Birthday Pick <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £2.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1017_Birthday Pick');" toggle-id="attr_addons_1017_BirthdayPick" id="attr_addons_1017_Birthday Pick"
name="attr_addons_1017_Birthday Pick">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_14_Red Wine 37.5cl">
<div class="tooltip" data-content="A rich and full bodied delicious French red wine perfect to say Cheers! ">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_(AddOn)_Drinks-RedWine37cl.jpg"
alt="Red Wine 37.5cl">
</span>
<span class="add-description"> Red Wine (37.5cl) <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £7.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_14_Red Wine 37.5cl');" toggle-id="attr_addons_14_RedWine37.5cl" id="attr_addons_14_Red Wine 37.5cl"
name="attr_addons_14_Red Wine 37.5cl">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1140_Happy Birthday Candle Add On 2024">
<div class="tooltip" data-content="A colourful Happy Birthday candle to make their day extra special!">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_(AddOn)_Candle-HappyBirthday.jpg"
alt="Happy Birthday Candle Add On 2024">
</span>
<span class="add-description"> Happy Birthday Candle <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £2.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1140_Happy Birthday Candle Add On 2024');" toggle-id="attr_addons_1140_HappyBirthdayCandleAddOn2024"
id="attr_addons_1140_Happy Birthday Candle Add On 2024" name="attr_addons_1140_Happy Birthday Candle Add On 2024">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1117_Lullaby Baby Postal">
<div class="tooltip" data-content="The perfect gift to celebrate a new baby with lovely lamb toys!">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_(AddOn)_PostalHamper-NewBaby-NewBaby.jpg"
alt="Lullaby Baby Postal">
</span>
<span class="add-description"> New Baby Gift <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £24.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1117_Lullaby Baby Postal');" toggle-id="attr_addons_1117_LullabyBabyPostal" id="attr_addons_1117_Lullaby Baby Postal"
name="attr_addons_1117_Lullaby Baby Postal">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1044_PP2204">
<div class="tooltip" data-content="A delightful pamper hamper filled with goodies!">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/pamper-hamper-ENJOY.jpg" alt="PP2204">
</span>
<span class="add-description"> Perfect Pamper Gift <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £16.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1044_PP2204');" toggle-id="attr_addons_1044_PP2204" id="attr_addons_1044_PP2204" name="attr_addons_1044_PP2204">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_111_Hand Blown Vase">
<div class="tooltip" data-content="Handmade artisan vase, each unique
">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/hand_blown_vase.png" alt="Hand Blown Vase">
</span>
<span class="add-description"> Hand Blown Vase <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £9.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_111_Hand Blown Vase');" toggle-id="attr_addons_111_HandBlownVase" id="attr_addons_111_Hand Blown Vase"
name="attr_addons_111_Hand Blown Vase">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1154_Lemon Slices - Valley Sweets">
<div class="tooltip" data-content="Delicious fizzy lemon sweet slices by Valley Sweets">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_(AddOn)_Sweets-FizzyLemon.jpg"
alt="Lemon Slices - Valley Sweets">
</span>
<span class="add-description"> Fizzy Lemon Sweets <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £3.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1154_Lemon Slices - Valley Sweets');" toggle-id="attr_addons_1154_LemonSlices-ValleySweets"
id="attr_addons_1154_Lemon Slices - Valley Sweets" name="attr_addons_1154_Lemon Slices - Valley Sweets">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1167_PP2206">
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/6eb9b3858437161c45549380690e1975.jpg"
alt="PP2206">
</span>
<span class="add-description"> Chocolate Gift Box Gift <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £16.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1167_PP2206');" toggle-id="attr_addons_1167_PP2206" id="attr_addons_1167_PP2206" name="attr_addons_1167_PP2206">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1141_Moet 75cl">
<div class="tooltip" data-content="The perfect extra for special occasions, delightful bottle of Moet 75cl Champagne">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF-MOET-75.CL.jpg" alt="Moet 75cl">
</span>
<span class="add-description"> Moet 75cl <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £65 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1141_Moet 75cl');" toggle-id="attr_addons_1141_Moet75cl" id="attr_addons_1141_Moet 75cl"
name="attr_addons_1141_Moet 75cl">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1046_Scented Occasion Candle">
<div class="tooltip" data-content="Beautifully frangrant candle, florist selected for the occasion
">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/occasion-candle.png"
alt="Scented Occasion Candle">
</span>
<span class="add-description"> Occasion Candle <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £4.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1046_Scented Occasion Candle');" toggle-id="attr_addons_1046_ScentedOccasionCandle"
id="attr_addons_1046_Scented Occasion Candle" name="attr_addons_1046_Scented Occasion Candle">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1077_Tulip Bulbs Queen of the Night">
<div class="tooltip" data-content="10 Tulip Bulbs ready for planting!">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF-Tulip-bulb-addon (1).jpg"
alt="Tulip Bulbs Queen of the Night">
</span>
<span class="add-description"> Tulip Planting Bulbs <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £4.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1077_Tulip Bulbs Queen of the Night');" toggle-id="attr_addons_1077_TulipBulbsQueenoftheNight"
id="attr_addons_1077_Tulip Bulbs Queen of the Night" name="attr_addons_1077_Tulip Bulbs Queen of the Night">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_15_Moet 37.5cl">
<div class="tooltip" data-content="The perfect extra gift to make any occasion that much more extraordinary">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/half-moet.jpg" alt="Moet 37.5cl">
</span>
<span class="add-description"> Moet (37.5cl) <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £39.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_15_Moet 37.5cl');" toggle-id="attr_addons_15_Moet37.5cl" id="attr_addons_15_Moet 37.5cl"
name="attr_addons_15_Moet 37.5cl">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_58_Gin 5cl + Tonic">
<div class="tooltip" data-content="A classic cocktail drink perfect for celebrating ">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_(AddOn)_Drinks-GinTonic.jpg"
alt="Gin 5cl + Tonic">
</span>
<span class="add-description"> Gin 5cl + Tonic <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £4.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_58_Gin 5cl + Tonic');" toggle-id="attr_addons_58_Gin5cl+Tonic" id="attr_addons_58_Gin 5cl + Tonic"
name="attr_addons_58_Gin 5cl + Tonic">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1145_PP2202 Happy Birthday Postcard">
<div class="tooltip" data-content="Delectable Afternoon Tea postal gift, perfect for all occasions! ">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PF_(AddOn)_PostalHamper-AfternoonTea-Birthday.jpg"
alt="PP2202 Happy Birthday Postcard">
</span>
<span class="add-description"> Happy Birthday Afternoon Tea <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £16.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1145_PP2202 Happy Birthday Postcard');" toggle-id="attr_addons_1145_PP2202HappyBirthdayPostcard"
id="attr_addons_1145_PP2202 Happy Birthday Postcard" name="attr_addons_1145_PP2202 Happy Birthday Postcard">
<span class="checkmark"></span>
</label></div>
<div class="owl-item" style="width: 125.5px;"><label class="checkmark-container" for="attr_addons_1043_PP2205">
<div class="tooltip" data-content="A beautiful hamper for gin & tonic lovers!">
<i class="fa-regular fa-circle-info"></i>
</div>
<span class="add-img">
<img class="img-responsive center-block lazy"
src="https://images.prestigeflowers.co.uk/fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/https://www.prestigeflowers.co.uk/images/attributes/268204bda128f87ab4c086f233fd022b9a102906/PH-Gin-2024-Enjoy.jpg" alt="PP2205">
</span>
<span class="add-description"> Gin and Tonic Gift <!--<sub>Add on gift</sub>-->
<span class="add-cost"> £16.99 </span>
</span>
<input type="checkbox" onchange="AddonCrossSell($('#attr_messagecard').val()); checkAddon('suggested_attr_addons_1043_PP2205');" toggle-id="attr_addons_1043_PP2205" id="attr_addons_1043_PP2205" name="attr_addons_1043_PP2205">
<span class="checkmark"></span>
</label></div>
</div>
</div>
<div class="owl-nav">
<div class="owl-prev bg-brand disabled"></div>
<div class="owl-next bg-brand"></div>
</div>
<div class="owl-dots">
<div class="owl-dot active"><span></span></div>
<div class="owl-dot"><span></span></div>
<div class="owl-dot"><span></span></div>
<div class="owl-dot"><span></span></div>
<div class="owl-dot"><span></span></div>
<div class="owl-dot"><span></span></div>
<div class="owl-dot"><span></span></div>
<div class="owl-dot"><span></span></div>
<div class="owl-dot"><span></span></div>
<div class="owl-dot"><span></span></div>
</div>
</div>
<script>
$(function() {
var owl = $('.owl_addons_carousel').owlCarousel({
loop: false,
autoplay: false,
dots: true,
nav: true,
items: 3,
slideBy: 5,
lazyLoad: true,
lazyContent: true,
navText: ['', ''],
navClass: ['owl-prev bg-brand', 'owl-next bg-brand'],
dotsClass: 'owl-dots',
responsive: {
480: {
items: 3
},
768: {
items: 3
}
}
});
$(':checkbox[readonly]').on('click change', function() {
return false;
});
});
</script>
</div>
</div>
<div class="clearfix"></div>
<!-- End Addons -->
<div class="product-step-box step3">
<div class="product-step">
<span class="stage">
<i class="fa-regular fa-pen"></i>
</span>
<span class="product-step-title" onclick="$('#attr_messagecard').focus();"> Write a Message Card </span>
</div>
<div class="step-content personalised-card-wrapper ">
<script>
var apiURL = "https://cards.prestigeflowers.co.uk";
var currentURL = "content=charity-flowers&id=majestic&kk=a4c6294-192fec8cf9e-1bdcd&sfeed=&utm_source=kelkoouk&utm_medium=cpc&utm_campaign=kelkooclick&utm_source_platform=KelkooGroup&utm_term=Prestige+Flowers+Majestic";
var editURL =
"content=charity-flowers&id=majestic&kk=a4c6294-192fec8cf9e-1bdcd&sfeed=&utm_source=kelkoouk&utm_medium=cpc&utm_campaign=kelkooclick&utm_source_platform=KelkooGroup&utm_term=Prestige+Flowers+Majestic&action=update_personalised_card";
var useS3 = true;
</script>
<script src="https://www.prestigeflowers.co.uk/styles/main/js/popper.min.js" defer=""></script>
<link rel="preload" as="script" href="https://www.prestigeflowers.co.uk/styles/main/js/bootstrap.min.js">
<script src="https://www.prestigeflowers.co.uk/styles/main/js/bootstrap.min.js" defer=""></script>
<script src="https://www.prestigeflowers.co.uk/styles/main/printzware/js/jquery-3.1.0.min.js"></script>
<script src="https://www.prestigeflowers.co.uk/styles/main/printzware/js/jquery.ba-postmessage.js"></script>
<script src="https://www.prestigeflowers.co.uk/styles/main/printzware/js/printzware.js?v=268204bda128f87ab4c086f233fd022b9a102906"></script>
<script>
function workOutPersonalisedCards(date) {
var today_d = 3;
var today_h = 00;
var today_m = 03;
var today_y = 2024;
var card_cutoff_h = 18;
var card_cutoff_m = 0;
// Saturday cuts off at 4pm
if (today_d === 6) {
card_cutoff_h = 16;
card_cutoff_m = 0;
}
var cutoff = false;
var tomorrow_d = 07;
var today_day = 06;
// Only check time cutoffs if it's next day
if (date.getDate() === tomorrow_d) {
if (today_h == card_cutoff_h && (today_m >= card_cutoff_m && today_m <= 59)) {
cutoff = true;
}
if (today_h > card_cutoff_h) {
cutoff = true;
}
}
// Sunday cuts off at 2pm (peak only)
if (today_d === 0 && date.getDate() === tomorrow_d) {
cutoff = true; // Always cut off on Sunday
}
var is_unavailable_day = ((today_d === 6 && cutoff === true) || (today_d === 0 && cutoff === true));
// Do our date cut-off by special date.
var month = date.getUTCMonth() + 1; //months from 1-12
var day = date.getUTCDate();
var year = date.getUTCFullYear();
// Need to get it off?
if ((date.getDate() === (tomorrow_d) && cutoff === true) || is_unavailable_day === true) {
$('.add_personalised_message').fadeOut();
$('.message-card-standard').fadeIn();
$("#standard").prop("checked", true);
} else {
$('.add_personalised_message').fadeIn();
}
}
</script>
<script>
jQuery(document).ready(function() {
function selectStandardCard() {
jQuery('.message-card-standard').removeClass('hidden');
jQuery('.message-card-standard').fadeIn();
jQuery('#standard').prop("checked", true);
}
jQuery('.standard_card').click(selectStandardCard);
jQuery('#attr_messagecard').focus(selectStandardCard);
jQuery('.add_personalised_message').click(function() {
jQuery('#standard').prop("checked", false);
jQuery("#personalised").prop("checked", true);
jQuery('.message-card-standard').hide();
var check = $('#cards .pw-card-image a');
if (check.length) {
// Open the edit url
opendialog(check.data('edit-url'));
} else {
// New order
opendialog(
'https://cards.prestigeflowers.co.uk/open.php?h=69a31be016d684184e13825e7c687b6473d01aca8192f5084dfbfbd234cbaee4&c=485&t=card&f=open&m=&o=pw672ab26332f8b&tag=0&clientCat=&p=&ts=1730851427&url=https%3A%2F%2Fwww.prestigeflowers.co.uk'
);
}
var delivery_date_selected = $("#delivery_date").datepicker('getFormattedDate');
getDeliveryOptions(delivery_date_selected, '5709');
});
jQuery("#delivery_date").on("changeDate", function(event) {
workOutPersonalisedCards(event.date);
});
jQuery('#pwCancelButton').click(function() {
console.log("Close Modal");
jQuery('#pw_modal').animate({
display: "none",
}, 400);
});
workOutPersonalisedCards($('#delivery_date').datepicker('getDate'));
});
function removeMessageCard() {
jQuery('.standard_card').hide();
jQuery('.message-card-seperator').hide();
};
</script>
<div class="col-xs-12" style="margin-bottom: 20px;">
<label for="personalised" class="personalised-card-container add_personalised_message">
<div class="left-section">
<div class="checkmark-container">
<input class="non-pretty ignore" type="radio" name="attr_card_type" id="personalised" value="personalised">
<p class="personalised-card-option"> Add a Personalised Card </p>
</div>
<div class="personalised-card-message">
<ul>
<li>Luxury hand finished cards</li>
<li>Cards for all occasions</li>
<li>Choose from many designs</li>
<li>Easily upload your pictures</li>
</ul>
<span class="personalised-card-price"> + £3.99 <s>£5.99</s> </span>
</div>
</div>
<div class="right-section">
<div class="personalised-card-preview">
<div id="cards"></div>
<!-- Modal -->
<div class="modal fade" id="pw_modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" data-bs-backdrop="static">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-body">
<iframe id="pw_iframe" style="border: 0px;" src="about:blank" width="100%" height="100%"></iframe>
</div>
</div>
</div>
</div>
<div class="preview_container">
<img id="personalised_example" width="100" height="140"
src="data:image/gif;base64,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"
alt="">
<!-- <div class="personalise-button">Personalise</div> -->
</div>
</div>
</div>
</label>
</div><!--col-xs-12-->
<section class="message-card-section">
<label class="message-card-trigger " onclick="OpenVideo();">
<input class="ignore" type="checkbox" name="recorded_a_message" value="yes">
<!-- <input class="ignore" checked="checked" type="radio" name="attr_card_type" id="standard" value="standard" /> -->
<div class="checkmark-container">
<div class="checkmark"></div>
<p>Send a FREE recorded message</p>
</div>
<div class="how-it-works">
<div class="step">
<img src="https://images.prestigeflowers.co.uk/fetch/w_120/https://www.prestigeflowers.co.uk/images/video-message/record.jpg" alt="step 1">
<span>Record your message</span>
</div>
<div class="step">
<img src="https://images.prestigeflowers.co.uk/fetch/w_120/https://www.prestigeflowers.co.uk/images/video-message/send.jpg" alt="step 2">
<span>We'll send with your gift</span>
</div>
</div>
<button type="button" class="btn"> Record Now! </button>
</label>
<div class="message-card-record-container hidden" id="voice-recording-container">
<div class="message-card-wrapper">
<div class="message-card-record">
<div class="close-option-top">
<span class="btn" id="close-window-option" onclick="CloseWindow();">
<i class="fa fal fa-xmark"></i>
</span>
</div>
<div class="heading">
<div class="heading-track" id="heading-track">
<div class="video-message recording">
<div id="video-preview">
<div class="message-recording-preview" id="recording_container">
<div class="recording-preview hidden"></div>
<div class="recording-preview-text hidden"></div>
<video controls="" autoplay="" playsinline="" id="recording_element" style="width: 100%;" class="hidden"></video>
</div>
</div>
<div class="content">
<div class="recording"></div>
</div>
</div>
<div class="intro">
<img src="images/video-message/family-selfie-min-new.jpg" alt="step 1">
<div class="content">
<h1>Send your FREE video message with every gift</h1>
</div>
</div>
<div class="voice-message recording">
<div class="content">
<div class="visualiser">
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
</div>
<div id="voice-preview"></div>
<h1>Record a voice message</h1>
<p> Just <b>press the 'Record' button</b> to start recording your voice message, you will have up to 30 seconds.</p>
</div>
</div>
</div>
</div>
<div class="choices">
<div class="video-error hidden">
<h3 style="color: red;">Please allow access to your camera and microphone to start recording.</h3>
</div>
<div class="voice-error hidden">
<h3 style="color: red;">Please allow access to your microphone to start recording.</h3>
</div>
<div class="choices-heading">
<h3 class="start">Let's Get Started</h3>
<h3 class="finish hidden">Happy With Your Recording?</h3>
</div>
<div class="options-wrapper">
<div class="options-track" id="options-track">
<div class="video-recording">
<div class="back-option">
<span class="btn back-button" onclick="VoiceCancel()">
<i class="fa fal fa-chevron-left"></i>
</span>
<text>Back</text>
</div>
<div class="send-video-option hidden" onclick="CloseVideo(false, true);">
<span class="btn send-video-button" id="send-video">
<i class="fa fal fa-send"></i>
</span>
<text>Add To Order!</text>
</div>
<div class="completed-video-option hidden" onclick="ConfirmVideo();">
<span class="btn complete-video-button" id="complete_video">
<i class="fa fal fa-check"></i>
</span>
<text>Save & Review</text>
</div>
<div class="video-option">
<span class="btn voice-button" id="record_video_audio">
<i class="fa fal fa-video-camera"></i>
</span>
<text>Record</text>
</div>
<div id="video-finish-option"></div>
</div>
<div class="intro">
<div class="video-option">
<span class="btn voice-button" onclick="VideoStart();">
<i class="fa fal fa-video-camera"></i>
</span>
<text>Video Message</text>
</div>
<div class="voice-option">
<span class="btn voice-button" onclick="VoiceStart();">
<i class="fa fal fa-microphone"></i>
</span>
<text>Voice Message</text>
</div>
<div class="close-option">
<span class="btn" onclick="CloseVideo();">
<i class="fa fal fa-xmark"></i>
</span>
<text>Close</text>
</div>
</div>
<div class="voice-recording">
<div class="back-option">
<span class="btn back-button" onclick="VoiceCancel();">
<i class="fa fal fa-chevron-left"></i>
</span>
<text>Back</text>
</div>
<div class="voice-option">
<span class="btn voice-button" id="record_audio">
<i class="fa fal fa-microphone"></i>
</span>
<text>Record</text>
</div>
<div class="completed-voice-option hidden" onclick="CloseVideo(false, true);">
<span class="btn complete-voice-button" id="complete_voice">
<i class="fa fal fa-check"></i>
</span>
<text>Save & Send</text>
</div>
<div id="voice-finish-option"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<div id="stop-option" class="hidden">
<span class="btn stop-button" id="record_stop">
<i class="fa fal fa-stop"></i>
</span> Stop Recording
</div>
<script>
var quiet = false;
var old_trigger_content;
var video = document.querySelector("#recording_element");
var element = document.getElementById('recording_element');
element.muted = "muted";
function VideoStart() {
console.log("Video Start");
$('#heading-track').css("left", "0");
$('#options-track').css("left", "0");
$('#recording_container').appendTo('#video-preview');
$('#close-window-option').attr("onclick", "CloseRecording();");
$('#recording_container').children("video").removeAttr('controls');
$('#record_audio').children("video").removeClass('controls');
$('#recording_element').removeAttr("controls");
$('.recording-preview-text').removeClass("hidden");
element.muted = "muted";
if (navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).then(function(stream) {
video.srcObject = stream;
}).catch(function(err0r) {
console.log("Something went wrong!");
});
}
}
function VoiceStart() {
console.log("VoiceStart");
$('.visualiser').removeClass("completed").removeClass("active");
$('#heading-track').css("left", "-200%");
$('#options-track').css("left", "-200%");
}
function VoiceCancel() {
$('#heading-track').css("left", "-100%");
$('#options-track').css("left", "-100%");
$('#recording_element').attr("src", "");
$('.visualiser').removeClass("completed").removeClass("active");
$('#recording_element').addClass("hidden");
$('.video-option').removeClass("hidden");
$('.finish').addClass("hidden");
$('.start').removeClass("hidden");
$('.send-video-option').addClass("hidden");
$('.message-card-record').removeClass("preview");
$('.video-option').children("text").text("Video Message");
$('.voice-option').children("text").text("Voice Message");
$('.completed-voice-option').addClass("hidden");
$('.completed-video-option').addClass("hidden");
$('#attr_videomsg').val("");
}
function OpenVideo() {
$('#voice-recording-container').removeClass("hidden");
}
function ConfirmVideo() {
$('.message-card-record').addClass("preview");
$('.send-video-option').removeClass("hidden");
$('.completed-video-option').addClass("hidden");
$('.start').addClass("hidden");
$(".finish").removeClass("hidden");
$('.video-option').addClass("hidden");
}
function CloseVideo(quiet = true, success = false) {
$('#voice-recording-container').addClass("hidden");
quiet = quiet;
if (success) {
old_trigger_content = $('.message-card-trigger .how-it-works').html();
$('.message-card-trigger .how-it-works').html("");
$('#recording_element').appendTo('.message-card-trigger .how-it-works');
$('.message-card-trigger').attr("onClick", "");
$('.message-card-trigger .btn').text("Start Again!").click(function() {
console.log("click");
$('.message-card-trigger .btn').text("Get Started");
$('.message-card-trigger .btn').unbind('click');
$('.message-card-trigger').attr("onClick", "OpenVideo();");
$('#recording_element').appendTo('#video-preview');
$('.message-card-trigger .how-it-works').html(old_trigger_content);
VoiceCancel();
OpenVideo();
});
}
// AwaitUpload();
}
function CloseWindow() {
$('#voice-recording-container').addClass("hidden");
quiet = quiet;
};
function CloseRecording() {
$('#voice-recording-container').addClass("hidden");
$('#heading-track').css("left", "-100%");
$('#options-track').css("left", "-100%");
};
function CloseStartedRecording() {
$('#voice-recording-container').addClass("hidden");
quiet = quiet;
recorder.stream.getTracks().forEach(function(track) {
track.stop();
});
$('#heading-track').css("left", "-100%");
$('#options-track').css("left", "-100%");
$('#recording_element').attr("src", "");
$('.visualiser').removeClass("completed").removeClass("active");
$('#recording_element').addClass("hidden");
$('.video-option').removeClass("hidden");
$('.finish').addClass("hidden");
$('.start').removeClass("hidden");
$('.send-video-option').addClass("hidden");
$('.message-card-record').removeClass("preview");
$('.recording-preview').addClass("hidden");
$('.video-option').children("text").text("Video Message");
$('.voice-option').children("text").text("Voice Message");
$('.completed-voice-option').addClass("hidden");
$('.completed-video-option').addClass("hidden");
$('.video-message').removeClass("active");
$('#stop-option').addClass('hidden');
$('#record_video_audio').removeClass('hidden');
$('#record_audio').removeClass('hidden');
$('.video-option').removeClass("hidden");
$('.voice-option').removeClass("hidden");
$('.back-option').removeClass("hidden");
$('#attr_videomsg').val("");
};
function AwaitUpload() {
$('#submit').attr("disabled", "true");
$('#submit').addClass("disabled");
$('#submit').text("Uploading...");
}
</script>
<style>
.hidden {
display: none;
}
</style>
<script>
'use strict';
// Last time updated: 2021-03-09 3:20:23 AM UTC
// ________________
// RecordRTC v5.6.2
// Open-Sourced: https://github.com/muaz-khan/RecordRTC
// --------------------------------------------------
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// --------------------------------------------------
"use strict";
function RecordRTC(mediaStream, config) {
function startRecording(config2) {
return config.disableLogs || console.log("RecordRTC version: ", self.version), config2 && (config = new RecordRTCConfiguration(mediaStream, config2)), config.disableLogs || console.log("started recording " + config.type + " stream."),
mediaRecorder ? (mediaRecorder.clearRecordedData(), mediaRecorder.record(), setState("recording"), self.recordingDuration && handleRecordingDuration(), self) : (initRecorder(function() {
self.recordingDuration && handleRecordingDuration()
}), self)
}
function initRecorder(initCallback) {
initCallback && (config.initCallback = function() {
initCallback(), initCallback = config.initCallback = null
});
var Recorder = new GetRecorderType(mediaStream, config);
mediaRecorder = new Recorder(mediaStream, config), mediaRecorder.record(), setState("recording"), config.disableLogs || console.log("Initialized recorderType:", mediaRecorder.constructor.name, "for output-type:", config.type)
}
function stopRecording(callback) {
function _callback(__blob) {
if (!mediaRecorder) return void("function" == typeof callback.call ? callback.call(self, "") : callback(""));
Object.keys(mediaRecorder).forEach(function(key) {
"function" != typeof mediaRecorder[key] && (self[key] = mediaRecorder[key])
});
var blob = mediaRecorder.blob;
if (!blob) {
if (!__blob) throw "Recording failed.";
mediaRecorder.blob = blob = __blob
}
if (blob && !config.disableLogs && console.log(blob.type, "->", bytesToSize(blob.size)), callback) {
var url;
try {
url = URL.createObjectURL(blob)
} catch (e) {}
"function" == typeof callback.call ? callback.call(self, url) : callback(url)
}
config.autoWriteToDisk && getDataURL(function(dataURL) {
var parameter = {};
parameter[config.type + "Blob"] = dataURL, DiskStorage.Store(parameter)
})
}
return callback = callback || function() {}, mediaRecorder ? "paused" === self.state ? (self.resumeRecording(), void setTimeout(function() {
stopRecording(callback)
}, 1)) : ("recording" === self.state || config.disableLogs || console.warn('Recording state should be: "recording", however current state is: ', self.state), config.disableLogs || console.log("Stopped recording " + config.type +
" stream."), "gif" !== config.type ? mediaRecorder.stop(_callback) : (mediaRecorder.stop(), _callback()), void setState("stopped")) : void warningLog()
}
function pauseRecording() {
return mediaRecorder ? "recording" !== self.state ? void(config.disableLogs || console.warn("Unable to pause the recording. Recording state: ", self.state)) : (setState("paused"), mediaRecorder.pause(), void(config.disableLogs || console
.log("Paused recording."))) : void warningLog()
}
function resumeRecording() {
return mediaRecorder ? "paused" !== self.state ? void(config.disableLogs || console.warn("Unable to resume the recording. Recording state: ", self.state)) : (setState("recording"), mediaRecorder.resume(), void(config.disableLogs ||
console.log("Resumed recording."))) : void warningLog()
}
function readFile(_blob) {
postMessage((new FileReaderSync).readAsDataURL(_blob))
}
function getDataURL(callback, _mediaRecorder) {
function processInWebWorker(_function) {
try {
var blob = URL.createObjectURL(new Blob([_function.toString(), "this.onmessage = function (eee) {" + _function.name + "(eee.data);}"], {
type: "application/javascript"
})),
worker = new Worker(blob);
return URL.revokeObjectURL(blob), worker
} catch (e) {}
}
if (!callback) throw "Pass a callback function over getDataURL.";
var blob = _mediaRecorder ? _mediaRecorder.blob : (mediaRecorder || {}).blob;
if (!blob) return config.disableLogs || console.warn("Blob encoder did not finish its job yet."), void setTimeout(function() {
getDataURL(callback, _mediaRecorder)
}, 1e3);
if ("undefined" == typeof Worker || navigator.mozGetUserMedia) {
var reader = new FileReader;
reader.readAsDataURL(blob), reader.onload = function(event) {
callback(event.target.result)
}
} else {
var webWorker = processInWebWorker(readFile);
webWorker.onmessage = function(event) {
callback(event.data)
}, webWorker.postMessage(blob)
}
}
function handleRecordingDuration(counter) {
if (counter = counter || 0, "paused" === self.state) return void setTimeout(function() {
handleRecordingDuration(counter)
}, 1e3);
if ("stopped" !== self.state) {
if (counter >= self.recordingDuration) return void stopRecording(self.onRecordingStopped);
counter += 1e3, setTimeout(function() {
handleRecordingDuration(counter)
}, 1e3)
}
}
function setState(state) {
self && (self.state = state, "function" == typeof self.onStateChanged.call ? self.onStateChanged.call(self, state) : self.onStateChanged(state))
}
function warningLog() {
config.disableLogs !== !0 && console.warn(WARNING)
}
if (!mediaStream) throw "First parameter is required.";
config = config || {
type: "video"
}, config = new RecordRTCConfiguration(mediaStream, config);
var mediaRecorder, self = this,
WARNING = 'It seems that recorder is destroyed or "startRecording" is not invoked for ' + config.type + " recorder.",
returnObject = {
startRecording: startRecording,
stopRecording: stopRecording,
pauseRecording: pauseRecording,
resumeRecording: resumeRecording,
initRecorder: initRecorder,
setRecordingDuration: function(recordingDuration, callback) {
if ("undefined" == typeof recordingDuration) throw "recordingDuration is required.";
if ("number" != typeof recordingDuration) throw "recordingDuration must be a number.";
return self.recordingDuration = recordingDuration, self.onRecordingStopped = callback || function() {}, {
onRecordingStopped: function(callback) {
self.onRecordingStopped = callback
}
}
},
clearRecordedData: function() {
return mediaRecorder ? (mediaRecorder.clearRecordedData(), void(config.disableLogs || console.log("Cleared old recorded data."))) : void warningLog()
},
getBlob: function() {
return mediaRecorder ? mediaRecorder.blob : void warningLog()
},
getDataURL: getDataURL,
toURL: function() {
return mediaRecorder ? URL.createObjectURL(mediaRecorder.blob) : void warningLog()
},
getInternalRecorder: function() {
return mediaRecorder
},
save: function(fileName) {
return mediaRecorder ? void invokeSaveAsDialog(mediaRecorder.blob, fileName) : void warningLog()
},
getFromDisk: function(callback) {
return mediaRecorder ? void RecordRTC.getFromDisk(config.type, callback) : void warningLog()
},
setAdvertisementArray: function(arrayOfWebPImages) {
config.advertisement = [];
for (var length = arrayOfWebPImages.length, i = 0; i < length; i++) config.advertisement.push({
duration: i,
image: arrayOfWebPImages[i]
})
},
blob: null,
bufferSize: 0,
sampleRate: 0,
buffer: null,
reset: function() {
"recording" !== self.state || config.disableLogs || console.warn("Stop an active recorder."), mediaRecorder && "function" == typeof mediaRecorder.clearRecordedData && mediaRecorder.clearRecordedData(), mediaRecorder = null,
setState("inactive"), self.blob = null
},
onStateChanged: function(state) {
config.disableLogs || console.log("Recorder state changed:", state)
},
state: "inactive",
getState: function() {
return self.state
},
destroy: function() {
var disableLogsCache = config.disableLogs;
config = {
disableLogs: !0
}, self.reset(), setState("destroyed"), returnObject = self = null, Storage.AudioContextConstructor && (Storage.AudioContextConstructor.close(), Storage.AudioContextConstructor = null), config.disableLogs = disableLogsCache,
config.disableLogs || console.log("RecordRTC is destroyed.")
},
version: "5.6.2"
};
if (!this) return self = returnObject, returnObject;
for (var prop in returnObject) this[prop] = returnObject[prop];
return self = this, returnObject
}
function RecordRTCConfiguration(mediaStream, config) {
return config.recorderType || config.type || (config.audio && config.video ? config.type = "video" : config.audio && !config.video && (config.type = "audio")), config.recorderType && !config.type && (config.recorderType ===
WhammyRecorder || config.recorderType === CanvasRecorder || "undefined" != typeof WebAssemblyRecorder && config.recorderType === WebAssemblyRecorder ? config.type = "video" : config.recorderType === GifRecorder ? config.type = "gif" :
config.recorderType === StereoAudioRecorder ? config.type = "audio" : config.recorderType === MediaStreamRecorder && (getTracks(mediaStream, "audio").length && getTracks(mediaStream, "video").length ? config.type = "video" : !getTracks(
mediaStream, "audio").length && getTracks(mediaStream, "video").length ? config.type = "video" : getTracks(mediaStream, "audio").length && !getTracks(mediaStream, "video").length && (config.type = "audio"))), "undefined" !=
typeof MediaStreamRecorder && "undefined" != typeof MediaRecorder && "requestData" in MediaRecorder.prototype && (config.mimeType || (config.mimeType = "video/webm"), config.type || (config.type = config.mimeType.split("/")[0]), !config
.bitsPerSecond), config.type || (config.mimeType && (config.type = config.mimeType.split("/")[0]), config.type || (config.type = "audio")), config
}
function GetRecorderType(mediaStream, config) {
var recorder;
return (isChrome || isEdge || isOpera) && (recorder = StereoAudioRecorder), "undefined" != typeof MediaRecorder && "requestData" in MediaRecorder.prototype && !isChrome && (recorder = MediaStreamRecorder), "video" === config.type && (
isChrome || isOpera) && (recorder = WhammyRecorder, "undefined" != typeof WebAssemblyRecorder && "undefined" != typeof ReadableStream && (recorder = WebAssemblyRecorder)), "gif" === config.type && (recorder = GifRecorder), "canvas" ===
config.type && (recorder = CanvasRecorder), isMediaRecorderCompatible() && recorder !== CanvasRecorder && recorder !== GifRecorder && "undefined" != typeof MediaRecorder && "requestData" in MediaRecorder.prototype && (getTracks(
mediaStream, "video").length || getTracks(mediaStream, "audio").length) && ("audio" === config.type ? "function" == typeof MediaRecorder.isTypeSupported && MediaRecorder.isTypeSupported("audio/webm") && (recorder =
MediaStreamRecorder) : "function" == typeof MediaRecorder.isTypeSupported && MediaRecorder.isTypeSupported("video/webm") && (recorder = MediaStreamRecorder)), mediaStream instanceof Array && mediaStream.length && (recorder =
MultiStreamRecorder), config.recorderType && (recorder = config.recorderType), !config.disableLogs && recorder && recorder.name && console.log("Using recorderType:", recorder.name || recorder.constructor.name), !recorder && isSafari &&
(recorder = MediaStreamRecorder), recorder
}
function MRecordRTC(mediaStream) {
this.addStream = function(_mediaStream) {
_mediaStream && (mediaStream = _mediaStream)
}, this.mediaType = {
audio: !0,
video: !0
}, this.startRecording = function() {
var recorderType, mediaType = this.mediaType,
mimeType = this.mimeType || {
audio: null,
video: null,
gif: null
};
if ("function" != typeof mediaType.audio && isMediaRecorderCompatible() && !getTracks(mediaStream, "audio").length && (mediaType.audio = !1), "function" != typeof mediaType.video && isMediaRecorderCompatible() && !getTracks(mediaStream,
"video").length && (mediaType.video = !1), "function" != typeof mediaType.gif && isMediaRecorderCompatible() && !getTracks(mediaStream, "video").length && (mediaType.gif = !1), !mediaType.audio && !mediaType.video && !mediaType.gif)
throw "MediaStream must have either audio or video tracks.";
if (mediaType.audio && (recorderType = null, "function" == typeof mediaType.audio && (recorderType = mediaType.audio), this.audioRecorder = new RecordRTC(mediaStream, {
type: "audio",
bufferSize: this.bufferSize,
sampleRate: this.sampleRate,
numberOfAudioChannels: this.numberOfAudioChannels || 2,
disableLogs: this.disableLogs,
recorderType: recorderType,
mimeType: mimeType.audio,
timeSlice: this.timeSlice,
onTimeStamp: this.onTimeStamp
}), mediaType.video || this.audioRecorder.startRecording()), mediaType.video) {
recorderType = null, "function" == typeof mediaType.video && (recorderType = mediaType.video);
var newStream = mediaStream;
if (isMediaRecorderCompatible() && mediaType.audio && "function" == typeof mediaType.audio) {
var videoTrack = getTracks(mediaStream, "video")[0];
isFirefox ? (newStream = new MediaStream, newStream.addTrack(videoTrack), recorderType && recorderType === WhammyRecorder && (recorderType = MediaStreamRecorder)) : (newStream = new MediaStream, newStream.addTrack(videoTrack))
}
this.videoRecorder = new RecordRTC(newStream, {
type: "video",
video: this.video,
canvas: this.canvas,
frameInterval: this.frameInterval || 10,
disableLogs: this.disableLogs,
recorderType: recorderType,
mimeType: mimeType.video,
timeSlice: this.timeSlice,
onTimeStamp: this.onTimeStamp,
workerPath: this.workerPath,
webAssemblyPath: this.webAssemblyPath,
frameRate: this.frameRate,
bitrate: this.bitrate
}), mediaType.audio || this.videoRecorder.startRecording()
}
if (mediaType.audio && mediaType.video) {
var self = this,
isSingleRecorder = isMediaRecorderCompatible() === !0;
mediaType.audio instanceof StereoAudioRecorder && mediaType.video ? isSingleRecorder = !1 : mediaType.audio !== !0 && mediaType.video !== !0 && mediaType.audio !== mediaType.video && (isSingleRecorder = !1), isSingleRecorder === !0 ?
(self.audioRecorder = null, self.videoRecorder.startRecording()) : self.videoRecorder.initRecorder(function() {
self.audioRecorder.initRecorder(function() {
self.videoRecorder.startRecording(), self.audioRecorder.startRecording()
})
})
}
mediaType.gif && (recorderType = null, "function" == typeof mediaType.gif && (recorderType = mediaType.gif), this.gifRecorder = new RecordRTC(mediaStream, {
type: "gif",
frameRate: this.frameRate || 200,
quality: this.quality || 10,
disableLogs: this.disableLogs,
recorderType: recorderType,
mimeType: mimeType.gif
}), this.gifRecorder.startRecording())
}, this.stopRecording = function(callback) {
callback = callback || function() {}, this.audioRecorder && this.audioRecorder.stopRecording(function(blobURL) {
callback(blobURL, "audio")
}), this.videoRecorder && this.videoRecorder.stopRecording(function(blobURL) {
callback(blobURL, "video")
}), this.gifRecorder && this.gifRecorder.stopRecording(function(blobURL) {
callback(blobURL, "gif")
})
}, this.pauseRecording = function() {
this.audioRecorder && this.audioRecorder.pauseRecording(), this.videoRecorder && this.videoRecorder.pauseRecording(), this.gifRecorder && this.gifRecorder.pauseRecording()
}, this.resumeRecording = function() {
this.audioRecorder && this.audioRecorder.resumeRecording(), this.videoRecorder && this.videoRecorder.resumeRecording(), this.gifRecorder && this.gifRecorder.resumeRecording()
}, this.getBlob = function(callback) {
var output = {};
return this.audioRecorder && (output.audio = this.audioRecorder.getBlob()), this.videoRecorder && (output.video = this.videoRecorder.getBlob()), this.gifRecorder && (output.gif = this.gifRecorder.getBlob()), callback && callback(
output), output
}, this.destroy = function() {
this.audioRecorder && (this.audioRecorder.destroy(), this.audioRecorder = null), this.videoRecorder && (this.videoRecorder.destroy(), this.videoRecorder = null), this.gifRecorder && (this.gifRecorder.destroy(), this.gifRecorder = null)
}, this.getDataURL = function(callback) {
function getDataURL(blob, callback00) {
if ("undefined" != typeof Worker) {
var webWorker = processInWebWorker(function(_blob) {
postMessage((new FileReaderSync).readAsDataURL(_blob))
});
webWorker.onmessage = function(event) {
callback00(event.data)
}, webWorker.postMessage(blob)
} else {
var reader = new FileReader;
reader.readAsDataURL(blob), reader.onload = function(event) {
callback00(event.target.result)
}
}
}
function processInWebWorker(_function) {
var url, blob = URL.createObjectURL(new Blob([_function.toString(), "this.onmessage = function (eee) {" + _function.name + "(eee.data);}"], {
type: "application/javascript"
})),
worker = new Worker(blob);
if ("undefined" != typeof URL) url = URL;
else {
if ("undefined" == typeof webkitURL) throw "Neither URL nor webkitURL detected.";
url = webkitURL
}
return url.revokeObjectURL(blob), worker
}
this.getBlob(function(blob) {
blob.audio && blob.video ? getDataURL(blob.audio, function(_audioDataURL) {
getDataURL(blob.video, function(_videoDataURL) {
callback({
audio: _audioDataURL,
video: _videoDataURL
})
})
}) : blob.audio ? getDataURL(blob.audio, function(_audioDataURL) {
callback({
audio: _audioDataURL
})
}) : blob.video && getDataURL(blob.video, function(_videoDataURL) {
callback({
video: _videoDataURL
})
})
})
}, this.writeToDisk = function() {
RecordRTC.writeToDisk({
audio: this.audioRecorder,
video: this.videoRecorder,
gif: this.gifRecorder
})
}, this.save = function(args) {
args = args || {
audio: !0,
video: !0,
gif: !0
}, args.audio && this.audioRecorder && this.audioRecorder.save("string" == typeof args.audio ? args.audio : ""), args.video && this.videoRecorder && this.videoRecorder.save("string" == typeof args.video ? args.video : ""), args.gif &&
this.gifRecorder && this.gifRecorder.save("string" == typeof args.gif ? args.gif : "")
}
}
function bytesToSize(bytes) {
var k = 1e3,
sizes = ["Bytes", "KB", "MB", "GB", "TB"];
if (0 === bytes) return "0 Bytes";
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(k)), 10);
return (bytes / Math.pow(k, i)).toPrecision(3) + " " + sizes[i]
}
function invokeSaveAsDialog(file, fileName) {
if (!file) throw "Blob object is required.";
if (!file.type) try {
file.type = "video/webm"
} catch (e) {}
var fileExtension = (file.type || "video/webm").split("/")[1];
if (fileExtension.indexOf(";") !== -1 && (fileExtension = fileExtension.split(";")[0]), fileName && fileName.indexOf(".") !== -1) {
var splitted = fileName.split(".");
fileName = splitted[0], fileExtension = splitted[1]
}
var fileFullName = (fileName || Math.round(9999999999 * Math.random()) + 888888888) + "." + fileExtension;
if ("undefined" != typeof navigator.msSaveOrOpenBlob) return navigator.msSaveOrOpenBlob(file, fileFullName);
if ("undefined" != typeof navigator.msSaveBlob) return navigator.msSaveBlob(file, fileFullName);
var hyperlink = document.createElement("a");
hyperlink.href = URL.createObjectURL(file), hyperlink.download = fileFullName, hyperlink.style = "display:none;opacity:0;color:transparent;", (document.body || document.documentElement).appendChild(hyperlink), "function" == typeof hyperlink
.click ? hyperlink.click() : (hyperlink.target = "_blank", hyperlink.dispatchEvent(new MouseEvent("click", {
view: window,
bubbles: !0,
cancelable: !0
}))), URL.revokeObjectURL(hyperlink.href)
}
function isElectron() {
return "undefined" != typeof window && "object" == typeof window.process && "renderer" === window.process.type || (!("undefined" == typeof process || "object" != typeof process.versions || !process.versions.electron) || "object" ==
typeof navigator && "string" == typeof navigator.userAgent && navigator.userAgent.indexOf("Electron") >= 0)
}
function getTracks(stream, kind) {
return stream && stream.getTracks ? stream.getTracks().filter(function(t) {
return t.kind === (kind || "audio")
}) : []
}
function setSrcObject(stream, element) {
"srcObject" in element ? element.srcObject = stream : "mozSrcObject" in element ? element.mozSrcObject = stream : element.srcObject = stream
}
function getSeekableBlob(inputBlob, callback) {
if ("undefined" == typeof EBML) throw new Error("Please link: https://www.webrtc-experiment.com/EBML.js");
var reader = new EBML.Reader,
decoder = new EBML.Decoder,
tools = EBML.tools,
fileReader = new FileReader;
fileReader.onload = function(e) {
var ebmlElms = decoder.decode(this.result);
ebmlElms.forEach(function(element) {
reader.read(element)
}), reader.stop();
var refinedMetadataBuf = tools.makeMetadataSeekable(reader.metadatas, reader.duration, reader.cues),
body = this.result.slice(reader.metadataSize),
newBlob = new Blob([refinedMetadataBuf, body], {
type: "video/webm"
});
callback(newBlob)
}, fileReader.readAsArrayBuffer(inputBlob)
}
function isMediaRecorderCompatible() {
if (isFirefox || isSafari || isEdge) return !0;
var verOffset, ix, nAgt = (navigator.appVersion, navigator.userAgent),
fullVersion = "" + parseFloat(navigator.appVersion),
majorVersion = parseInt(navigator.appVersion, 10);
return (isChrome || isOpera) && (verOffset = nAgt.indexOf("Chrome"), fullVersion = nAgt.substring(verOffset + 7)), (ix = fullVersion.indexOf(";")) !== -1 && (fullVersion = fullVersion.substring(0, ix)), (ix = fullVersion.indexOf(" ")) !== -
1 && (fullVersion = fullVersion.substring(0, ix)), majorVersion = parseInt("" + fullVersion, 10), isNaN(majorVersion) && (fullVersion = "" + parseFloat(navigator.appVersion), majorVersion = parseInt(navigator.appVersion, 10)),
majorVersion >= 49
}
function MediaStreamRecorder(mediaStream, config) {
function updateTimeStamp() {
self.timestamps.push((new Date).getTime()), "function" == typeof config.onTimeStamp && config.onTimeStamp(self.timestamps[self.timestamps.length - 1], self.timestamps)
}
function getMimeType(secondObject) {
return mediaRecorder && mediaRecorder.mimeType ? mediaRecorder.mimeType : secondObject.mimeType || "video/webm"
}
function clearRecordedDataCB() {
arrayOfBlobs = [], mediaRecorder = null, self.timestamps = []
}
function isMediaStreamActive() {
if ("active" in mediaStream) {
if (!mediaStream.active) return !1
} else if ("ended" in mediaStream && mediaStream.ended) return !1;
return !0
}
var self = this;
if ("undefined" == typeof mediaStream) throw 'First argument "MediaStream" is required.';
if ("undefined" == typeof MediaRecorder) throw "Your browser does not support the Media Recorder API. Please try other modules e.g. WhammyRecorder or StereoAudioRecorder.";
if (config = config || {
mimeType: "video/webm"
}, "audio" === config.type) {
if (getTracks(mediaStream, "video").length && getTracks(mediaStream, "audio").length) {
var stream;
navigator.mozGetUserMedia ? (stream = new MediaStream, stream.addTrack(getTracks(mediaStream, "audio")[0])) : stream = new MediaStream(getTracks(mediaStream, "audio")), mediaStream = stream
}
config.mimeType && config.mimeType.toString().toLowerCase().indexOf("audio") !== -1 || (config.mimeType = isChrome ? "audio/webm" : "audio/ogg"), config.mimeType && "audio/ogg" !== config.mimeType.toString().toLowerCase() && navigator
.mozGetUserMedia && (config.mimeType = "audio/ogg")
}
var arrayOfBlobs = [];
this.getArrayOfBlobs = function() {
return arrayOfBlobs
}, this.record = function() {
self.blob = null, self.clearRecordedData(), self.timestamps = [], allStates = [], arrayOfBlobs = [];
var recorderHints = config;
config.disableLogs || console.log("Passing following config over MediaRecorder API.", recorderHints), mediaRecorder && (mediaRecorder = null), isChrome && !isMediaRecorderCompatible() && (recorderHints = "video/vp8"), "function" ==
typeof MediaRecorder.isTypeSupported && recorderHints.mimeType && (MediaRecorder.isTypeSupported(recorderHints.mimeType) || (config.disableLogs || console.warn("MediaRecorder API seems unable to record mimeType:", recorderHints
.mimeType), recorderHints.mimeType = "audio" === config.type ? "audio/webm" : "video/webm"));
try {
mediaRecorder = new MediaRecorder(mediaStream, recorderHints), config.mimeType = recorderHints.mimeType
} catch (e) {
mediaRecorder = new MediaRecorder(mediaStream)
}
recorderHints.mimeType && !MediaRecorder.isTypeSupported && "canRecordMimeType" in mediaRecorder && mediaRecorder.canRecordMimeType(recorderHints.mimeType) === !1 && (config.disableLogs || console.warn(
"MediaRecorder API seems unable to record mimeType:", recorderHints.mimeType)), mediaRecorder.ondataavailable = function(e) {
if (e.data && allStates.push("ondataavailable: " + bytesToSize(e.data.size)), "number" != typeof config.timeSlice) {
if (!e.data || !e.data.size || e.data.size < 100 || self.blob) return void(self.recordingCallback && (self.recordingCallback(new Blob([], {
type: getMimeType(recorderHints)
})), self.recordingCallback = null));
self.blob = config.getNativeBlob ? e.data : new Blob([e.data], {
type: getMimeType(recorderHints)
}), self.recordingCallback && (self.recordingCallback(self.blob), self.recordingCallback = null)
} else if (e.data && e.data.size && (arrayOfBlobs.push(e.data), updateTimeStamp(), "function" == typeof config.ondataavailable)) {
var blob = config.getNativeBlob ? e.data : new Blob([e.data], {
type: getMimeType(recorderHints)
});
config.ondataavailable(blob)
}
}, mediaRecorder.onstart = function() {
allStates.push("started")
}, mediaRecorder.onpause = function() {
allStates.push("paused")
}, mediaRecorder.onresume = function() {
allStates.push("resumed")
}, mediaRecorder.onstop = function() {
allStates.push("stopped")
}, mediaRecorder.onerror = function(error) {
error && (error.name || (error.name = "UnknownError"), allStates.push("error: " + error), config.disableLogs || (error.name.toString().toLowerCase().indexOf("invalidstate") !== -1 ? console.error(
"The MediaRecorder is not in a state in which the proposed operation is allowed to be executed.", error) : error.name.toString().toLowerCase().indexOf("notsupported") !== -1 ? console.error("MIME type (", recorderHints
.mimeType, ") is not supported.", error) : error.name.toString().toLowerCase().indexOf("security") !== -1 ? console.error("MediaRecorder security error", error) : "OutOfMemory" === error.name ? console.error(
"The UA has exhaused the available memory. User agents SHOULD provide as much additional information as possible in the message attribute.", error) : "IllegalStreamModification" === error.name ? console.error(
"A modification to the stream has occurred that makes it impossible to continue recording. An example would be the addition of a Track while recording is occurring. User agents SHOULD provide as much additional information as possible in the message attribute.",
error) : "OtherRecordingError" === error.name ? console.error("Used for an fatal error other than those listed above. User agents SHOULD provide as much additional information as possible in the message attribute.", error) :
"GenericError" === error.name ? console.error("The UA cannot provide the codec or recording option that has been requested.", error) : console.error("MediaRecorder Error", error)), function(looper) {
return !self.manuallyStopped && mediaRecorder && "inactive" === mediaRecorder.state ? (delete config.timeslice, void mediaRecorder.start(6e5)) : void setTimeout(looper, 1e3)
}(), "inactive" !== mediaRecorder.state && "stopped" !== mediaRecorder.state && mediaRecorder.stop())
}, "number" == typeof config.timeSlice ? (updateTimeStamp(), mediaRecorder.start(config.timeSlice)) : mediaRecorder.start(36e5), config.initCallback && config.initCallback()
}, this.timestamps = [], this.stop = function(callback) {
callback = callback || function() {}, self.manuallyStopped = !0, mediaRecorder && (this.recordingCallback = callback, "recording" === mediaRecorder.state && mediaRecorder.stop(), "number" == typeof config.timeSlice && setTimeout(
function() {
self.blob = new Blob(arrayOfBlobs, {
type: getMimeType(config)
}), self.recordingCallback(self.blob)
}, 100))
}, this.pause = function() {
mediaRecorder && "recording" === mediaRecorder.state && mediaRecorder.pause()
}, this.resume = function() {
mediaRecorder && "paused" === mediaRecorder.state && mediaRecorder.resume()
}, this.clearRecordedData = function() {
mediaRecorder && "recording" === mediaRecorder.state && self.stop(clearRecordedDataCB), clearRecordedDataCB()
};
var mediaRecorder;
this.getInternalRecorder = function() {
return mediaRecorder
}, this.blob = null, this.getState = function() {
return mediaRecorder ? mediaRecorder.state || "inactive" : "inactive"
};
var allStates = [];
this.getAllStates = function() {
return allStates
}, "undefined" == typeof config.checkForInactiveTracks && (config.checkForInactiveTracks = !1);
var self = this;
! function looper() {
if (mediaRecorder && config.checkForInactiveTracks !== !1) return isMediaStreamActive() === !1 ? (config.disableLogs || console.log("MediaStream seems stopped."), void self.stop()) : void setTimeout(looper, 1e3)
}(), this.name = "MediaStreamRecorder", this.toString = function() {
return this.name
}
}
function StereoAudioRecorder(mediaStream, config) {
function isMediaStreamActive() {
if (config.checkForInactiveTracks === !1) return !0;
if ("active" in mediaStream) {
if (!mediaStream.active) return !1
} else if ("ended" in mediaStream && mediaStream.ended) return !1;
return !0
}
function mergeLeftRightBuffers(config, callback) {
function mergeAudioBuffers(config, cb) {
function interpolateArray(data, newSampleRate, oldSampleRate) {
var fitCount = Math.round(data.length * (newSampleRate / oldSampleRate)),
newData = [],
springFactor = Number((data.length - 1) / (fitCount - 1));
newData[0] = data[0];
for (var i = 1; i < fitCount - 1; i++) {
var tmp = i * springFactor,
before = Number(Math.floor(tmp)).toFixed(),
after = Number(Math.ceil(tmp)).toFixed(),
atPoint = tmp - before;
newData[i] = linearInterpolate(data[before], data[after], atPoint)
}
return newData[fitCount - 1] = data[data.length - 1], newData
}
function linearInterpolate(before, after, atPoint) {
return before + (after - before) * atPoint
}
function mergeBuffers(channelBuffer, rLength) {
for (var result = new Float64Array(rLength), offset = 0, lng = channelBuffer.length, i = 0; i < lng; i++) {
var buffer = channelBuffer[i];
result.set(buffer, offset), offset += buffer.length
}
return result
}
function interleave(leftChannel, rightChannel) {
for (var length = leftChannel.length + rightChannel.length, result = new Float64Array(length), inputIndex = 0, index = 0; index < length;) result[index++] = leftChannel[inputIndex], result[index++] = rightChannel[inputIndex],
inputIndex++;
return result
}
function writeUTFBytes(view, offset, string) {
for (var lng = string.length, i = 0; i < lng; i++) view.setUint8(offset + i, string.charCodeAt(i))
}
var numberOfAudioChannels = config.numberOfAudioChannels,
leftBuffers = config.leftBuffers.slice(0),
rightBuffers = config.rightBuffers.slice(0),
sampleRate = config.sampleRate,
internalInterleavedLength = config.internalInterleavedLength,
desiredSampRate = config.desiredSampRate;
2 === numberOfAudioChannels && (leftBuffers = mergeBuffers(leftBuffers, internalInterleavedLength), rightBuffers = mergeBuffers(rightBuffers, internalInterleavedLength), desiredSampRate && (leftBuffers = interpolateArray(leftBuffers,
desiredSampRate, sampleRate), rightBuffers = interpolateArray(rightBuffers, desiredSampRate, sampleRate))), 1 === numberOfAudioChannels && (leftBuffers = mergeBuffers(leftBuffers, internalInterleavedLength), desiredSampRate && (
leftBuffers = interpolateArray(leftBuffers, desiredSampRate, sampleRate))), desiredSampRate && (sampleRate = desiredSampRate);
var interleaved;
2 === numberOfAudioChannels && (interleaved = interleave(leftBuffers, rightBuffers)), 1 === numberOfAudioChannels && (interleaved = leftBuffers);
var interleavedLength = interleaved.length,
resultingBufferLength = 44 + 2 * interleavedLength,
buffer = new ArrayBuffer(resultingBufferLength),
view = new DataView(buffer);
writeUTFBytes(view, 0, "RIFF"), view.setUint32(4, 36 + 2 * interleavedLength, !0), writeUTFBytes(view, 8, "WAVE"), writeUTFBytes(view, 12, "fmt "), view.setUint32(16, 16, !0), view.setUint16(20, 1, !0), view.setUint16(22,
numberOfAudioChannels, !0), view.setUint32(24, sampleRate, !0), view.setUint32(28, sampleRate * numberOfAudioChannels * 2, !0), view.setUint16(32, 2 * numberOfAudioChannels, !0), view.setUint16(34, 16, !0), writeUTFBytes(view, 36,
"data"), view.setUint32(40, 2 * interleavedLength, !0);
for (var lng = interleavedLength, index = 44, volume = 1, i = 0; i < lng; i++) view.setInt16(index, interleaved[i] * (32767 * volume), !0), index += 2;
return cb ? cb({
buffer: buffer,
view: view
}) : void postMessage({
buffer: buffer,
view: view
})
}
if (config.noWorker) return void mergeAudioBuffers(config, function(data) {
callback(data.buffer, data.view)
});
var webWorker = processInWebWorker(mergeAudioBuffers);
webWorker.onmessage = function(event) {
callback(event.data.buffer, event.data.view), URL.revokeObjectURL(webWorker.workerURL), webWorker.terminate()
}, webWorker.postMessage(config)
}
function processInWebWorker(_function) {
var workerURL = URL.createObjectURL(new Blob([_function.toString(), ";this.onmessage = function (eee) {" + _function.name + "(eee.data);}"], {
type: "application/javascript"
})),
worker = new Worker(workerURL);
return worker.workerURL = workerURL, worker
}
function resetVariables() {
leftchannel = [], rightchannel = [], recordingLength = 0, isAudioProcessStarted = !1, recording = !1, isPaused = !1, context = null, self.leftchannel = leftchannel, self.rightchannel = rightchannel, self.numberOfAudioChannels =
numberOfAudioChannels, self.desiredSampRate = desiredSampRate, self.sampleRate = sampleRate, self.recordingLength = recordingLength, intervalsBasedBuffers = {
left: [],
right: [],
recordingLength: 0
}
}
function clearRecordedDataCB() {
jsAudioNode && (jsAudioNode.onaudioprocess = null, jsAudioNode.disconnect(), jsAudioNode = null), audioInput && (audioInput.disconnect(), audioInput = null), resetVariables()
}
function onAudioProcessDataAvailable(e) {
if (!isPaused) {
if (isMediaStreamActive() === !1 && (config.disableLogs || console.log("MediaStream seems stopped."), jsAudioNode.disconnect(), recording = !1), !recording) return void(audioInput && (audioInput.disconnect(), audioInput = null));
isAudioProcessStarted || (isAudioProcessStarted = !0, config.onAudioProcessStarted && config.onAudioProcessStarted(), config.initCallback && config.initCallback());
var left = e.inputBuffer.getChannelData(0),
chLeft = new Float32Array(left);
if (leftchannel.push(chLeft), 2 === numberOfAudioChannels) {
var right = e.inputBuffer.getChannelData(1),
chRight = new Float32Array(right);
rightchannel.push(chRight)
}
recordingLength += bufferSize, self.recordingLength = recordingLength, "undefined" != typeof config.timeSlice && (intervalsBasedBuffers.recordingLength += bufferSize, intervalsBasedBuffers.left.push(chLeft), 2 ===
numberOfAudioChannels && intervalsBasedBuffers.right.push(chRight))
}
}
function looper() {
recording && "function" == typeof config.ondataavailable && "undefined" != typeof config.timeSlice && (intervalsBasedBuffers.left.length ? (mergeLeftRightBuffers({
desiredSampRate: desiredSampRate,
sampleRate: sampleRate,
numberOfAudioChannels: numberOfAudioChannels,
internalInterleavedLength: intervalsBasedBuffers.recordingLength,
leftBuffers: intervalsBasedBuffers.left,
rightBuffers: 1 === numberOfAudioChannels ? [] : intervalsBasedBuffers.right
}, function(buffer, view) {
var blob = new Blob([view], {
type: "audio/wav"
});
config.ondataavailable(blob), setTimeout(looper, config.timeSlice)
}), intervalsBasedBuffers = {
left: [],
right: [],
recordingLength: 0
}) : setTimeout(looper, config.timeSlice))
}
if (!getTracks(mediaStream, "audio").length) throw "Your stream has no audio tracks.";
config = config || {};
var jsAudioNode, self = this,
leftchannel = [],
rightchannel = [],
recording = !1,
recordingLength = 0,
numberOfAudioChannels = 2,
desiredSampRate = config.desiredSampRate;
config.leftChannel === !0 && (numberOfAudioChannels = 1), 1 === config.numberOfAudioChannels && (numberOfAudioChannels = 1), (!numberOfAudioChannels || numberOfAudioChannels < 1) && (numberOfAudioChannels = 2), config.disableLogs || console
.log("StereoAudioRecorder is set to record number of channels: " + numberOfAudioChannels), "undefined" == typeof config.checkForInactiveTracks && (config.checkForInactiveTracks = !0), this.record = function() {
if (isMediaStreamActive() === !1) throw "Please make sure MediaStream is active.";
resetVariables(), isAudioProcessStarted = isPaused = !1, recording = !0, "undefined" != typeof config.timeSlice && looper()
}, this.stop = function(callback) {
callback = callback || function() {}, recording = !1, mergeLeftRightBuffers({
desiredSampRate: desiredSampRate,
sampleRate: sampleRate,
numberOfAudioChannels: numberOfAudioChannels,
internalInterleavedLength: recordingLength,
leftBuffers: leftchannel,
rightBuffers: 1 === numberOfAudioChannels ? [] : rightchannel,
noWorker: config.noWorker
}, function(buffer, view) {
self.blob = new Blob([view], {
type: "audio/wav"
}), self.buffer = new ArrayBuffer(view.buffer.byteLength), self.view = view, self.sampleRate = desiredSampRate || sampleRate, self.bufferSize = bufferSize, self.length = recordingLength, isAudioProcessStarted = !1, callback &&
callback(self.blob)
})
}, "undefined" == typeof RecordRTC.Storage && (RecordRTC.Storage = {
AudioContextConstructor: null,
AudioContext: window.AudioContext || window.webkitAudioContext
}), RecordRTC.Storage.AudioContextConstructor && "closed" !== RecordRTC.Storage.AudioContextConstructor.state || (RecordRTC.Storage.AudioContextConstructor = new RecordRTC.Storage.AudioContext);
var context = RecordRTC.Storage.AudioContextConstructor,
audioInput = context.createMediaStreamSource(mediaStream),
legalBufferValues = [0, 256, 512, 1024, 2048, 4096, 8192, 16384],
bufferSize = "undefined" == typeof config.bufferSize ? 4096 : config.bufferSize;
if (legalBufferValues.indexOf(bufferSize) === -1 && (config.disableLogs || console.log("Legal values for buffer-size are " + JSON.stringify(legalBufferValues, null, "\t"))), context.createJavaScriptNode) jsAudioNode = context
.createJavaScriptNode(bufferSize, numberOfAudioChannels, numberOfAudioChannels);
else {
if (!context.createScriptProcessor) throw "WebAudio API has no support on this browser.";
jsAudioNode = context.createScriptProcessor(bufferSize, numberOfAudioChannels, numberOfAudioChannels)
}
audioInput.connect(jsAudioNode), config.bufferSize || (bufferSize = jsAudioNode.bufferSize);
var sampleRate = "undefined" != typeof config.sampleRate ? config.sampleRate : context.sampleRate || 44100;
(sampleRate < 22050 || sampleRate > 96e3) && (config.disableLogs || console.log("sample-rate must be under range 22050 and 96000.")), config.disableLogs || config.desiredSampRate && console.log("Desired sample-rate: " + config
.desiredSampRate);
var isPaused = !1;
this.pause = function() {
isPaused = !0
}, this.resume = function() {
if (isMediaStreamActive() === !1) throw "Please make sure MediaStream is active.";
return recording ? void(isPaused = !1) : (config.disableLogs || console.log("Seems recording has been restarted."), void this.record())
}, this.clearRecordedData = function() {
config.checkForInactiveTracks = !1, recording && this.stop(clearRecordedDataCB), clearRecordedDataCB()
}, this.name = "StereoAudioRecorder", this.toString = function() {
return this.name
};
var isAudioProcessStarted = !1;
jsAudioNode.onaudioprocess = onAudioProcessDataAvailable, context.createMediaStreamDestination ? jsAudioNode.connect(context.createMediaStreamDestination()) : jsAudioNode.connect(context.destination), this.leftchannel = leftchannel, this
.rightchannel = rightchannel, this.numberOfAudioChannels = numberOfAudioChannels, this.desiredSampRate = desiredSampRate, this.sampleRate = sampleRate, self.recordingLength = recordingLength;
var intervalsBasedBuffers = {
left: [],
right: [],
recordingLength: 0
}
}
function CanvasRecorder(htmlElement, config) {
function clearRecordedDataCB() {
whammy.frames = [], isRecording = !1, isPausedRecording = !1
}
function cloneCanvas() {
var newCanvas = document.createElement("canvas"),
context = newCanvas.getContext("2d");
return newCanvas.width = htmlElement.width, newCanvas.height = htmlElement.height, context.drawImage(htmlElement, 0, 0), newCanvas
}
function drawCanvasFrame() {
if (isPausedRecording) return lastTime = (new Date).getTime(), setTimeout(drawCanvasFrame, 500);
if ("canvas" === htmlElement.nodeName.toLowerCase()) {
var duration = (new Date).getTime() - lastTime;
return lastTime = (new Date).getTime(), whammy.frames.push({
image: cloneCanvas(),
duration: duration
}), void(isRecording && setTimeout(drawCanvasFrame, config.frameInterval))
}
html2canvas(htmlElement, {
grabMouse: "undefined" == typeof config.showMousePointer || config.showMousePointer,
onrendered: function(canvas) {
var duration = (new Date).getTime() - lastTime;
return duration ? (lastTime = (new Date).getTime(), whammy.frames.push({
image: canvas.toDataURL("image/webp", 1),
duration: duration
}), void(isRecording && setTimeout(drawCanvasFrame, config.frameInterval))) : setTimeout(drawCanvasFrame, config.frameInterval)
}
})
}
if ("undefined" == typeof html2canvas) throw "Please link: https://www.webrtc-experiment.com/screenshot.js";
config = config || {}, config.frameInterval || (config.frameInterval = 10);
var isCanvasSupportsStreamCapturing = !1;
["captureStream", "mozCaptureStream", "webkitCaptureStream"].forEach(function(item) {
item in document.createElement("canvas") && (isCanvasSupportsStreamCapturing = !0)
});
var _isChrome = !(!window.webkitRTCPeerConnection && !window.webkitGetUserMedia || !window.chrome),
chromeVersion = 50,
matchArray = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
_isChrome && matchArray && matchArray[2] && (chromeVersion = parseInt(matchArray[2], 10)), _isChrome && chromeVersion < 52 && (isCanvasSupportsStreamCapturing = !1), config.useWhammyRecorder && (isCanvasSupportsStreamCapturing = !1);
var globalCanvas, mediaStreamRecorder;
if (isCanvasSupportsStreamCapturing)
if (config.disableLogs || console.log("Your browser supports both MediRecorder API and canvas.captureStream!"), htmlElement instanceof HTMLCanvasElement) globalCanvas = htmlElement;
else {
if (!(htmlElement instanceof CanvasRenderingContext2D)) throw "Please pass either HTMLCanvasElement or CanvasRenderingContext2D.";
globalCanvas = htmlElement.canvas
}
else navigator.mozGetUserMedia && (config.disableLogs || console.error("Canvas recording is NOT supported in Firefox."));
var isRecording;
this.record = function() {
if (isRecording = !0, isCanvasSupportsStreamCapturing && !config.useWhammyRecorder) {
var canvasMediaStream;
"captureStream" in globalCanvas ? canvasMediaStream = globalCanvas.captureStream(25) : "mozCaptureStream" in globalCanvas ? canvasMediaStream = globalCanvas.mozCaptureStream(25) : "webkitCaptureStream" in globalCanvas && (
canvasMediaStream = globalCanvas.webkitCaptureStream(25));
try {
var mdStream = new MediaStream;
mdStream.addTrack(getTracks(canvasMediaStream, "video")[0]), canvasMediaStream = mdStream
} catch (e) {}
if (!canvasMediaStream) throw "captureStream API are NOT available.";
mediaStreamRecorder = new MediaStreamRecorder(canvasMediaStream, {
mimeType: config.mimeType || "video/webm"
}), mediaStreamRecorder.record()
} else whammy.frames = [], lastTime = (new Date).getTime(), drawCanvasFrame();
config.initCallback && config.initCallback()
}, this.getWebPImages = function(callback) {
if ("canvas" !== htmlElement.nodeName.toLowerCase()) return void callback();
var framesLength = whammy.frames.length;
whammy.frames.forEach(function(frame, idx) {
var framesRemaining = framesLength - idx;
config.disableLogs || console.log(framesRemaining + "/" + framesLength + " frames remaining"), config.onEncodingCallback && config.onEncodingCallback(framesRemaining, framesLength);
var webp = frame.image.toDataURL("image/webp", 1);
whammy.frames[idx].image = webp
}), config.disableLogs || console.log("Generating WebM"), callback()
}, this.stop = function(callback) {
isRecording = !1;
var that = this;
return isCanvasSupportsStreamCapturing && mediaStreamRecorder ? void mediaStreamRecorder.stop(callback) : void this.getWebPImages(function() {
whammy.compile(function(blob) {
config.disableLogs || console.log("Recording finished!"), that.blob = blob, that.blob.forEach && (that.blob = new Blob([], {
type: "video/webm"
})), callback && callback(that.blob), whammy.frames = []
})
})
};
var isPausedRecording = !1;
this.pause = function() {
if (isPausedRecording = !0, mediaStreamRecorder instanceof MediaStreamRecorder) return void mediaStreamRecorder.pause()
}, this.resume = function() {
return isPausedRecording = !1, mediaStreamRecorder instanceof MediaStreamRecorder ? void mediaStreamRecorder.resume() : void(isRecording || this.record())
}, this.clearRecordedData = function() {
isRecording && this.stop(clearRecordedDataCB), clearRecordedDataCB()
}, this.name = "CanvasRecorder", this.toString = function() {
return this.name
};
var lastTime = (new Date).getTime(),
whammy = new Whammy.Video(100)
}
function WhammyRecorder(mediaStream, config) {
function drawFrames(frameInterval) {
frameInterval = "undefined" != typeof frameInterval ? frameInterval : 10;
var duration = (new Date).getTime() - lastTime;
return duration ? isPausedRecording ? (lastTime = (new Date).getTime(), setTimeout(drawFrames, 100)) : (lastTime = (new Date).getTime(), video.paused && video.play(), context.drawImage(video, 0, 0, canvas.width, canvas.height), whammy
.frames.push({
duration: duration,
image: canvas.toDataURL("image/webp")
}), void(isStopDrawing || setTimeout(drawFrames, frameInterval, frameInterval))) : setTimeout(drawFrames, frameInterval, frameInterval)
}
function asyncLoop(o) {
var i = -1,
length = o.length;
! function loop() {
return i++, i === length ? void o.callback() : void setTimeout(function() {
o.functionToLoop(loop, i)
}, 1)
}()
}
function dropBlackFrames(_frames, _framesToCheck, _pixTolerance, _frameTolerance, callback) {
var localCanvas = document.createElement("canvas");
localCanvas.width = canvas.width, localCanvas.height = canvas.height;
var context2d = localCanvas.getContext("2d"),
resultFrames = [],
checkUntilNotBlack = _framesToCheck === -1,
endCheckFrame = _framesToCheck && _framesToCheck > 0 && _framesToCheck <= _frames.length ? _framesToCheck : _frames.length,
sampleColor = {
r: 0,
g: 0,
b: 0
},
maxColorDifference = Math.sqrt(Math.pow(255, 2) + Math.pow(255, 2) + Math.pow(255, 2)),
pixTolerance = _pixTolerance && _pixTolerance >= 0 && _pixTolerance <= 1 ? _pixTolerance : 0,
frameTolerance = _frameTolerance && _frameTolerance >= 0 && _frameTolerance <= 1 ? _frameTolerance : 0,
doNotCheckNext = !1;
asyncLoop({
length: endCheckFrame,
functionToLoop: function(loop, f) {
var matchPixCount, endPixCheck, maxPixCount, finishImage = function() {
!doNotCheckNext && maxPixCount - matchPixCount <= maxPixCount * frameTolerance || (checkUntilNotBlack && (doNotCheckNext = !0), resultFrames.push(_frames[f])), loop()
};
if (doNotCheckNext) finishImage();
else {
var image = new Image;
image.onload = function() {
context2d.drawImage(image, 0, 0, canvas.width, canvas.height);
var imageData = context2d.getImageData(0, 0, canvas.width, canvas.height);
matchPixCount = 0, endPixCheck = imageData.data.length, maxPixCount = imageData.data.length / 4;
for (var pix = 0; pix < endPixCheck; pix += 4) {
var currentColor = {
r: imageData.data[pix],
g: imageData.data[pix + 1],
b: imageData.data[pix + 2]
},
colorDifference = Math.sqrt(Math.pow(currentColor.r - sampleColor.r, 2) + Math.pow(currentColor.g - sampleColor.g, 2) + Math.pow(currentColor.b - sampleColor.b, 2));
colorDifference <= maxColorDifference * pixTolerance && matchPixCount++
}
finishImage()
}, image.src = _frames[f].image
}
},
callback: function() {
resultFrames = resultFrames.concat(_frames.slice(endCheckFrame)), resultFrames.length <= 0 && resultFrames.push(_frames[_frames.length - 1]), callback(resultFrames)
}
})
}
function clearRecordedDataCB() {
whammy.frames = [], isStopDrawing = !0, isPausedRecording = !1
}
config = config || {}, config.frameInterval || (config.frameInterval = 10), config.disableLogs || console.log("Using frames-interval:", config.frameInterval), this.record = function() {
config.width || (config.width = 320), config.height || (config.height = 240), config.video || (config.video = {
width: config.width,
height: config.height
}), config.canvas || (config.canvas = {
width: config.width,
height: config.height
}), canvas.width = config.canvas.width || 320, canvas.height = config.canvas.height || 240, context = canvas.getContext("2d"), config.video && config.video instanceof HTMLVideoElement ? (video = config.video.cloneNode(), config
.initCallback && config.initCallback()) : (video = document.createElement("video"), setSrcObject(mediaStream, video), video.onloadedmetadata = function() {
config.initCallback && config.initCallback()
}, video.width = config.video.width, video.height = config.video.height), video.muted = !0, video.play(), lastTime = (new Date).getTime(), whammy = new Whammy.Video, config.disableLogs || (console.log("canvas resolutions", canvas
.width, "*", canvas.height), console.log("video width/height", video.width || canvas.width, "*", video.height || canvas.height)), drawFrames(config.frameInterval)
};
var isStopDrawing = !1;
this.stop = function(callback) {
callback = callback || function() {}, isStopDrawing = !0;
var _this = this;
setTimeout(function() {
dropBlackFrames(whammy.frames, -1, null, null, function(frames) {
whammy.frames = frames, config.advertisement && config.advertisement.length && (whammy.frames = config.advertisement.concat(whammy.frames)), whammy.compile(function(blob) {
_this.blob = blob, _this.blob.forEach && (_this.blob = new Blob([], {
type: "video/webm"
})), callback && callback(_this.blob)
})
})
}, 10)
};
var isPausedRecording = !1;
this.pause = function() {
isPausedRecording = !0
}, this.resume = function() {
isPausedRecording = !1, isStopDrawing && this.record()
}, this.clearRecordedData = function() {
isStopDrawing || this.stop(clearRecordedDataCB), clearRecordedDataCB()
}, this.name = "WhammyRecorder", this.toString = function() {
return this.name
};
var video, lastTime, whammy, canvas = document.createElement("canvas"),
context = canvas.getContext("2d")
}
function GifRecorder(mediaStream, config) {
function clearRecordedDataCB() {
gifEncoder && (gifEncoder.stream().bin = [])
}
if ("undefined" == typeof GIFEncoder) {
var script = document.createElement("script");
script.src = "https://www.webrtc-experiment.com/gif-recorder.js", (document.body || document.documentElement).appendChild(script)
}
config = config || {};
var isHTMLObject = mediaStream instanceof CanvasRenderingContext2D || mediaStream instanceof HTMLCanvasElement;
this.record = function() {
function drawVideoFrame(time) {
if (self.clearedRecordedData !== !0) {
if (isPausedRecording) return setTimeout(function() {
drawVideoFrame(time)
}, 100);
lastAnimationFrame = requestAnimationFrame(drawVideoFrame), void 0 === typeof lastFrameTime && (lastFrameTime = time), time - lastFrameTime < 90 || (!isHTMLObject && video.paused && video.play(), isHTMLObject || context.drawImage(
video, 0, 0, canvas.width, canvas.height), config.onGifPreview && config.onGifPreview(canvas.toDataURL("image/png")), gifEncoder.addFrame(context), lastFrameTime = time)
}
}
return "undefined" == typeof GIFEncoder ? void setTimeout(self.record, 1e3) : isLoadedMetaData ? (isHTMLObject || (config.width || (config.width = video.offsetWidth || 320), config.height || (config.height = video.offsetHeight || 240),
config.video || (config.video = {
width: config.width,
height: config.height
}), config.canvas || (config.canvas = {
width: config.width,
height: config.height
}), canvas.width = config.canvas.width || 320, canvas.height = config.canvas.height || 240, video.width = config.video.width || 320, video.height = config.video.height || 240), gifEncoder = new GIFEncoder, gifEncoder.setRepeat(0),
gifEncoder.setDelay(config.frameRate || 200), gifEncoder.setQuality(config.quality || 10), gifEncoder.start(), "function" == typeof config.onGifRecordingStarted && config.onGifRecordingStarted(), startTime = Date.now(),
lastAnimationFrame = requestAnimationFrame(drawVideoFrame), void(config.initCallback && config.initCallback())) : void setTimeout(self.record, 1e3)
}, this.stop = function(callback) {
callback = callback || function() {}, lastAnimationFrame && cancelAnimationFrame(lastAnimationFrame), endTime = Date.now(), this.blob = new Blob([new Uint8Array(gifEncoder.stream().bin)], {
type: "image/gif"
}), callback(this.blob), gifEncoder.stream().bin = []
};
var isPausedRecording = !1;
this.pause = function() {
isPausedRecording = !0
}, this.resume = function() {
isPausedRecording = !1
}, this.clearRecordedData = function() {
self.clearedRecordedData = !0, clearRecordedDataCB()
}, this.name = "GifRecorder", this.toString = function() {
return this.name
};
var canvas = document.createElement("canvas"),
context = canvas.getContext("2d");
isHTMLObject && (mediaStream instanceof CanvasRenderingContext2D ? (context = mediaStream, canvas = context.canvas) : mediaStream instanceof HTMLCanvasElement && (context = mediaStream.getContext("2d"), canvas = mediaStream));
var isLoadedMetaData = !0;
if (!isHTMLObject) {
var video = document.createElement("video");
video.muted = !0, video.autoplay = !0, video.playsInline = !0, isLoadedMetaData = !1, video.onloadedmetadata = function() {
isLoadedMetaData = !0
}, setSrcObject(mediaStream, video), video.play()
}
var startTime, endTime, lastFrameTime, gifEncoder, lastAnimationFrame = null,
self = this
}
function MultiStreamsMixer(arrayOfMediaStreams, elementClass) {
function setSrcObject(stream, element) {
"srcObject" in element ? element.srcObject = stream : "mozSrcObject" in element ? element.mozSrcObject = stream : element.srcObject = stream
}
function drawVideosToCanvas() {
if (!isStopDrawingFrames) {
var videosLength = videos.length,
fullcanvas = !1,
remaining = [];
if (videos.forEach(function(video) {
video.stream || (video.stream = {}), video.stream.fullcanvas ? fullcanvas = video : remaining.push(video)
}), fullcanvas) canvas.width = fullcanvas.stream.width, canvas.height = fullcanvas.stream.height;
else if (remaining.length) {
canvas.width = videosLength > 1 ? 2 * remaining[0].width : remaining[0].width;
var height = 1;
3 !== videosLength && 4 !== videosLength || (height = 2), 5 !== videosLength && 6 !== videosLength || (height = 3), 7 !== videosLength && 8 !== videosLength || (height = 4), 9 !== videosLength && 10 !== videosLength || (height = 5),
canvas.height = remaining[0].height * height
} else canvas.width = self.width || 360, canvas.height = self.height || 240;
fullcanvas && fullcanvas instanceof HTMLVideoElement && drawImage(fullcanvas), remaining.forEach(function(video, idx) {
drawImage(video, idx)
}), setTimeout(drawVideosToCanvas, self.frameInterval)
}
}
function drawImage(video, idx) {
if (!isStopDrawingFrames) {
var x = 0,
y = 0,
width = video.width,
height = video.height;
1 === idx && (x = video.width), 2 === idx && (y = video.height), 3 === idx && (x = video.width, y = video.height), 4 === idx && (y = 2 * video.height), 5 === idx && (x = video.width, y = 2 * video.height), 6 === idx && (y = 3 * video
.height), 7 === idx && (x = video.width, y = 3 * video.height), "undefined" != typeof video.stream.left && (x = video.stream.left), "undefined" != typeof video.stream.top && (y = video.stream.top), "undefined" != typeof video.stream
.width && (width = video.stream.width), "undefined" != typeof video.stream.height && (height = video.stream.height), context.drawImage(video, x, y, width, height), "function" == typeof video.stream.onRender && video.stream.onRender(
context, x, y, width, height, idx)
}
}
function getMixedStream() {
isStopDrawingFrames = !1;
var mixedVideoStream = getMixedVideoStream(),
mixedAudioStream = getMixedAudioStream();
mixedAudioStream && mixedAudioStream.getTracks().filter(function(t) {
return "audio" === t.kind
}).forEach(function(track) {
mixedVideoStream.addTrack(track)
});
var fullcanvas;
return arrayOfMediaStreams.forEach(function(stream) {
stream.fullcanvas && (fullcanvas = !0)
}), mixedVideoStream
}
function getMixedVideoStream() {
resetVideoStreams();
var capturedStream;
"captureStream" in canvas ? capturedStream = canvas.captureStream() : "mozCaptureStream" in canvas ? capturedStream = canvas.mozCaptureStream() : self.disableLogs || console.error(
"Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features");
var videoStream = new MediaStream;
return capturedStream.getTracks().filter(function(t) {
return "video" === t.kind
}).forEach(function(track) {
videoStream.addTrack(track)
}), canvas.stream = videoStream, videoStream
}
function getMixedAudioStream() {
Storage.AudioContextConstructor || (Storage.AudioContextConstructor = new Storage.AudioContext), self.audioContext = Storage.AudioContextConstructor, self.audioSources = [], self.useGainNode === !0 && (self.gainNode = self.audioContext
.createGain(), self.gainNode.connect(self.audioContext.destination), self.gainNode.gain.value = 0);
var audioTracksLength = 0;
if (arrayOfMediaStreams.forEach(function(stream) {
if (stream.getTracks().filter(function(t) {
return "audio" === t.kind
}).length) {
audioTracksLength++;
var audioSource = self.audioContext.createMediaStreamSource(stream);
self.useGainNode === !0 && audioSource.connect(self.gainNode), self.audioSources.push(audioSource)
}
}), audioTracksLength) return self.audioDestination = self.audioContext.createMediaStreamDestination(), self.audioSources.forEach(function(audioSource) {
audioSource.connect(self.audioDestination)
}), self.audioDestination.stream
}
function getVideo(stream) {
var video = document.createElement("video");
return setSrcObject(stream, video), video.className = elementClass, video.muted = !0, video.volume = 0, video.width = stream.width || self.width || 360, video.height = stream.height || self.height || 240, video.play(), video
}
function resetVideoStreams(streams) {
videos = [], streams = streams || arrayOfMediaStreams, streams.forEach(function(stream) {
if (stream.getTracks().filter(function(t) {
return "video" === t.kind
}).length) {
var video = getVideo(stream);
video.stream = stream, videos.push(video)
}
})
}
var browserFakeUserAgent = "Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45";
! function(that) {
"undefined" == typeof RecordRTC && that && "undefined" == typeof window && "undefined" != typeof global && (global.navigator = {
userAgent: browserFakeUserAgent,
getUserMedia: function() {}
}, global.console || (global.console = {}), "undefined" != typeof global.console.log && "undefined" != typeof global.console.error || (global.console.error = global.console.log = global.console.log || function() {
console.log(arguments)
}), "undefined" == typeof document && (that.document = {
documentElement: {
appendChild: function() {
return ""
}
}
}, document.createElement = document.captureStream = document.mozCaptureStream = function() {
var obj = {
getContext: function() {
return obj
},
play: function() {},
pause: function() {},
drawImage: function() {},
toDataURL: function() {
return ""
},
style: {}
};
return obj
}, that.HTMLVideoElement = function() {}), "undefined" == typeof location && (that.location = {
protocol: "file:",
href: "",
hash: ""
}), "undefined" == typeof screen && (that.screen = {
width: 0,
height: 0
}), "undefined" == typeof URL && (that.URL = {
createObjectURL: function() {
return ""
},
revokeObjectURL: function() {
return ""
}
}), that.window = global)
}("undefined" != typeof global ? global : null), elementClass = elementClass || "multi-streams-mixer";
var videos = [],
isStopDrawingFrames = !1,
canvas = document.createElement("canvas"),
context = canvas.getContext("2d");
canvas.style.opacity = 0, canvas.style.position = "absolute", canvas.style.zIndex = -1, canvas.style.top = "-1000em", canvas.style.left = "-1000em", canvas.className = elementClass, (document.body || document.documentElement).appendChild(
canvas), this.disableLogs = !1, this.frameInterval = 10, this.width = 360, this.height = 240, this.useGainNode = !0;
var self = this,
AudioContext = window.AudioContext;
"undefined" == typeof AudioContext && ("undefined" != typeof webkitAudioContext && (AudioContext = webkitAudioContext), "undefined" != typeof mozAudioContext && (AudioContext = mozAudioContext));
var URL = window.URL;
"undefined" == typeof URL && "undefined" != typeof webkitURL && (URL = webkitURL), "undefined" != typeof navigator && "undefined" == typeof navigator.getUserMedia && ("undefined" != typeof navigator.webkitGetUserMedia && (navigator
.getUserMedia = navigator.webkitGetUserMedia), "undefined" != typeof navigator.mozGetUserMedia && (navigator.getUserMedia = navigator.mozGetUserMedia));
var MediaStream = window.MediaStream;
"undefined" == typeof MediaStream && "undefined" != typeof webkitMediaStream && (MediaStream = webkitMediaStream), "undefined" != typeof MediaStream && "undefined" == typeof MediaStream.prototype.stop && (MediaStream.prototype.stop =
function() {
this.getTracks().forEach(function(track) {
track.stop()
})
});
var Storage = {};
"undefined" != typeof AudioContext ? Storage.AudioContext = AudioContext : "undefined" != typeof webkitAudioContext && (Storage.AudioContext = webkitAudioContext), this.startDrawingFrames = function() {
drawVideosToCanvas()
}, this.appendStreams = function(streams) {
if (!streams) throw "First parameter is required.";
streams instanceof Array || (streams = [streams]), streams.forEach(function(stream) {
var newStream = new MediaStream;
if (stream.getTracks().filter(function(t) {
return "video" === t.kind
}).length) {
var video = getVideo(stream);
video.stream = stream, videos.push(video), newStream.addTrack(stream.getTracks().filter(function(t) {
return "video" === t.kind
})[0])
}
if (stream.getTracks().filter(function(t) {
return "audio" === t.kind
}).length) {
var audioSource = self.audioContext.createMediaStreamSource(stream);
self.audioDestination = self.audioContext.createMediaStreamDestination(), audioSource.connect(self.audioDestination), newStream.addTrack(self.audioDestination.stream.getTracks().filter(function(t) {
return "audio" === t.kind
})[0])
}
arrayOfMediaStreams.push(newStream)
})
}, this.releaseStreams = function() {
videos = [], isStopDrawingFrames = !0, self.gainNode && (self.gainNode.disconnect(), self.gainNode = null), self.audioSources.length && (self.audioSources.forEach(function(source) {
source.disconnect()
}), self.audioSources = []), self.audioDestination && (self.audioDestination.disconnect(), self.audioDestination = null), self.audioContext && self.audioContext.close(), self.audioContext = null, context.clearRect(0, 0, canvas.width,
canvas.height), canvas.stream && (canvas.stream.stop(), canvas.stream = null)
}, this.resetVideoStreams = function(streams) {
!streams || streams instanceof Array || (streams = [streams]), resetVideoStreams(streams)
}, this.name = "MultiStreamsMixer", this.toString = function() {
return this.name
}, this.getMixedStream = getMixedStream
}
function MultiStreamRecorder(arrayOfMediaStreams, options) {
function getAllVideoTracks() {
var tracks = [];
return arrayOfMediaStreams.forEach(function(stream) {
getTracks(stream, "video").forEach(function(track) {
tracks.push(track)
})
}), tracks
}
arrayOfMediaStreams = arrayOfMediaStreams || [];
var mixer, mediaRecorder, self = this;
options = options || {
elementClass: "multi-streams-mixer",
mimeType: "video/webm",
video: {
width: 360,
height: 240
}
}, options.frameInterval || (options.frameInterval = 10), options.video || (options.video = {}), options.video.width || (options.video.width = 360), options.video.height || (options.video.height = 240), this.record = function() {
mixer = new MultiStreamsMixer(arrayOfMediaStreams, options.elementClass || "multi-streams-mixer"), getAllVideoTracks().length && (mixer.frameInterval = options.frameInterval || 10, mixer.width = options.video.width || 360, mixer
.height = options.video.height || 240, mixer.startDrawingFrames()), options.previewStream && "function" == typeof options.previewStream && options.previewStream(mixer.getMixedStream()), mediaRecorder = new MediaStreamRecorder(mixer
.getMixedStream(), options), mediaRecorder.record()
}, this.stop = function(callback) {
mediaRecorder && mediaRecorder.stop(function(blob) {
self.blob = blob, callback(blob), self.clearRecordedData()
})
}, this.pause = function() {
mediaRecorder && mediaRecorder.pause()
}, this.resume = function() {
mediaRecorder && mediaRecorder.resume()
}, this.clearRecordedData = function() {
mediaRecorder && (mediaRecorder.clearRecordedData(), mediaRecorder = null), mixer && (mixer.releaseStreams(), mixer = null)
}, this.addStreams = function(streams) {
if (!streams) throw "First parameter is required.";
streams instanceof Array || (streams = [streams]), arrayOfMediaStreams.concat(streams), mediaRecorder && mixer && (mixer.appendStreams(streams), options.previewStream && "function" == typeof options.previewStream && options
.previewStream(mixer.getMixedStream()))
}, this.resetVideoStreams = function(streams) {
mixer && (!streams || streams instanceof Array || (streams = [streams]), mixer.resetVideoStreams(streams))
}, this.getMixer = function() {
return mixer
}, this.name = "MultiStreamRecorder", this.toString = function() {
return this.name
}
}
function RecordRTCPromisesHandler(mediaStream, options) {
if (!this) throw 'Use "new RecordRTCPromisesHandler()"';
if ("undefined" == typeof mediaStream) throw 'First argument "MediaStream" is required.';
var self = this;
self.recordRTC = new RecordRTC(mediaStream, options), this.startRecording = function() {
return new Promise(function(resolve, reject) {
try {
self.recordRTC.startRecording(), resolve()
} catch (e) {
reject(e)
}
})
}, this.stopRecording = function() {
return new Promise(function(resolve, reject) {
try {
self.recordRTC.stopRecording(function(url) {
return self.blob = self.recordRTC.getBlob(), self.blob && self.blob.size ? void resolve(url) : void reject("Empty blob.", self.blob)
})
} catch (e) {
reject(e)
}
})
}, this.pauseRecording = function() {
return new Promise(function(resolve, reject) {
try {
self.recordRTC.pauseRecording(), resolve()
} catch (e) {
reject(e)
}
})
}, this.resumeRecording = function() {
return new Promise(function(resolve, reject) {
try {
self.recordRTC.resumeRecording(), resolve()
} catch (e) {
reject(e)
}
})
}, this.getDataURL = function(callback) {
return new Promise(function(resolve, reject) {
try {
self.recordRTC.getDataURL(function(dataURL) {
resolve(dataURL)
})
} catch (e) {
reject(e)
}
})
}, this.getBlob = function() {
return new Promise(function(resolve, reject) {
try {
resolve(self.recordRTC.getBlob())
} catch (e) {
reject(e)
}
})
}, this.getInternalRecorder = function() {
return new Promise(function(resolve, reject) {
try {
resolve(self.recordRTC.getInternalRecorder())
} catch (e) {
reject(e)
}
})
}, this.reset = function() {
return new Promise(function(resolve, reject) {
try {
resolve(self.recordRTC.reset())
} catch (e) {
reject(e)
}
})
}, this.destroy = function() {
return new Promise(function(resolve, reject) {
try {
resolve(self.recordRTC.destroy())
} catch (e) {
reject(e)
}
})
}, this.getState = function() {
return new Promise(function(resolve, reject) {
try {
resolve(self.recordRTC.getState())
} catch (e) {
reject(e)
}
})
}, this.blob = null, this.version = "5.6.2"
}
function WebAssemblyRecorder(stream, config) {
function cameraStream() {
return new ReadableStream({
start: function(controller) {
var cvs = document.createElement("canvas"),
video = document.createElement("video"),
first = !0;
video.srcObject = stream, video.muted = !0, video.height = config.height, video.width = config.width, video.volume = 0, video.onplaying = function() {
cvs.width = config.width, cvs.height = config.height;
var ctx = cvs.getContext("2d"),
frameTimeout = 1e3 / config.frameRate,
cameraTimer = setInterval(function() {
if (finished && (clearInterval(cameraTimer), controller.close()), first && (first = !1, config.onVideoProcessStarted && config.onVideoProcessStarted()), ctx.drawImage(video, 0, 0), "closed" !== controller
._controlledReadableStream.state) try {
controller.enqueue(ctx.getImageData(0, 0, config.width, config.height))
} catch (e) {}
}, frameTimeout)
}, video.play()
}
})
}
function startRecording(stream, buffer) {
if (!config.workerPath && !buffer) return finished = !1, void fetch("https://unpkg.com/webm-wasm@latest/dist/webm-worker.js").then(function(r) {
r.arrayBuffer().then(function(buffer) {
startRecording(stream, buffer)
})
});
if (!config.workerPath && buffer instanceof ArrayBuffer) {
var blob = new Blob([buffer], {
type: "text/javascript"
});
config.workerPath = URL.createObjectURL(blob)
}
config.workerPath || console.error("workerPath parameter is missing."), worker = new Worker(config.workerPath), worker.postMessage(config.webAssemblyPath || "https://unpkg.com/webm-wasm@latest/dist/webm-wasm.wasm"), worker
.addEventListener("message", function(event) {
"READY" === event.data ? (worker.postMessage({
width: config.width,
height: config.height,
bitrate: config.bitrate || 1200,
timebaseDen: config.frameRate || 30,
realtime: config.realtime
}), cameraStream().pipeTo(new WritableStream({
write: function(image) {
return finished ? void console.error("Got image, but recorder is finished!") : void worker.postMessage(image.data.buffer, [image.data.buffer])
}
}))) : event.data && (isPaused || arrayOfBuffers.push(event.data))
})
}
function terminate(callback) {
return worker ? (worker.addEventListener("message", function(event) {
null === event.data && (worker.terminate(), worker = null, callback && callback())
}), void worker.postMessage(null)) : void(callback && callback())
}
"undefined" != typeof ReadableStream && "undefined" != typeof WritableStream || console.error("Following polyfill is strongly recommended: https://unpkg.com/@mattiasbuelens/web-streams-polyfill/dist/polyfill.min.js"), config = config || {},
config.width = config.width || 640, config.height = config.height || 480, config.frameRate = config.frameRate || 30, config.bitrate = config.bitrate || 1200, config.realtime = config.realtime || !0;
var finished, worker;
this.record = function() {
arrayOfBuffers = [], isPaused = !1, this.blob = null, startRecording(stream), "function" == typeof config.initCallback && config.initCallback()
};
var isPaused;
this.pause = function() {
isPaused = !0
}, this.resume = function() {
isPaused = !1
};
var arrayOfBuffers = [];
this.stop = function(callback) {
finished = !0;
var recorder = this;
terminate(function() {
recorder.blob = new Blob(arrayOfBuffers, {
type: "video/webm"
}), callback(recorder.blob)
})
}, this.name = "WebAssemblyRecorder", this.toString = function() {
return this.name
}, this.clearRecordedData = function() {
arrayOfBuffers = [], isPaused = !1, this.blob = null
}, this.blob = null
}
RecordRTC.version = "5.6.2", "undefined" != typeof module && (module.exports = RecordRTC), "function" == typeof define && define.amd && define("RecordRTC", [], function() {
return RecordRTC
}), RecordRTC.getFromDisk = function(type, callback) {
if (!callback) throw "callback is mandatory.";
console.log("Getting recorded " + ("all" === type ? "blobs" : type + " blob ") + " from disk!"), DiskStorage.Fetch(function(dataURL, _type) {
"all" !== type && _type === type + "Blob" && callback && callback(dataURL), "all" === type && callback && callback(dataURL, _type.replace("Blob", ""))
})
}, RecordRTC.writeToDisk = function(options) {
console.log("Writing recorded blob(s) to disk!"), options = options || {}, options.audio && options.video && options.gif ? options.audio.getDataURL(function(audioDataURL) {
options.video.getDataURL(function(videoDataURL) {
options.gif.getDataURL(function(gifDataURL) {
DiskStorage.Store({
audioBlob: audioDataURL,
videoBlob: videoDataURL,
gifBlob: gifDataURL
})
})
})
}) : options.audio && options.video ? options.audio.getDataURL(function(audioDataURL) {
options.video.getDataURL(function(videoDataURL) {
DiskStorage.Store({
audioBlob: audioDataURL,
videoBlob: videoDataURL
})
})
}) : options.audio && options.gif ? options.audio.getDataURL(function(audioDataURL) {
options.gif.getDataURL(function(gifDataURL) {
DiskStorage.Store({
audioBlob: audioDataURL,
gifBlob: gifDataURL
})
})
}) : options.video && options.gif ? options.video.getDataURL(function(videoDataURL) {
options.gif.getDataURL(function(gifDataURL) {
DiskStorage.Store({
videoBlob: videoDataURL,
gifBlob: gifDataURL
})
})
}) : options.audio ? options.audio.getDataURL(function(audioDataURL) {
DiskStorage.Store({
audioBlob: audioDataURL
})
}) : options.video ? options.video.getDataURL(function(videoDataURL) {
DiskStorage.Store({
videoBlob: videoDataURL
})
}) : options.gif && options.gif.getDataURL(function(gifDataURL) {
DiskStorage.Store({
gifBlob: gifDataURL
})
})
}, MRecordRTC.getFromDisk = RecordRTC.getFromDisk, MRecordRTC.writeToDisk = RecordRTC.writeToDisk, "undefined" != typeof RecordRTC && (RecordRTC.MRecordRTC = MRecordRTC);
var browserFakeUserAgent = "Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45";
! function(that) {
that && "undefined" == typeof window && "undefined" != typeof global && (global.navigator = {
userAgent: browserFakeUserAgent,
getUserMedia: function() {}
}, global.console || (global.console = {}), "undefined" != typeof global.console.log && "undefined" != typeof global.console.error || (global.console.error = global.console.log = global.console.log || function() {
console.log(arguments)
}), "undefined" == typeof document && (that.document = {
documentElement: {
appendChild: function() {
return ""
}
}
}, document.createElement = document.captureStream = document.mozCaptureStream = function() {
var obj = {
getContext: function() {
return obj
},
play: function() {},
pause: function() {},
drawImage: function() {},
toDataURL: function() {
return ""
},
style: {}
};
return obj
}, that.HTMLVideoElement = function() {}), "undefined" == typeof location && (that.location = {
protocol: "file:",
href: "",
hash: ""
}), "undefined" == typeof screen && (that.screen = {
width: 0,
height: 0
}), "undefined" == typeof URL && (that.URL = {
createObjectURL: function() {
return ""
},
revokeObjectURL: function() {
return ""
}
}), that.window = global)
}("undefined" != typeof global ? global : null);
var requestAnimationFrame = window.requestAnimationFrame;
if ("undefined" == typeof requestAnimationFrame)
if ("undefined" != typeof webkitRequestAnimationFrame) requestAnimationFrame = webkitRequestAnimationFrame;
else if ("undefined" != typeof mozRequestAnimationFrame) requestAnimationFrame = mozRequestAnimationFrame;
else if ("undefined" != typeof msRequestAnimationFrame) requestAnimationFrame = msRequestAnimationFrame;
else if ("undefined" == typeof requestAnimationFrame) {
var lastTime = 0;
requestAnimationFrame = function(callback, element) {
var currTime = (new Date).getTime(),
timeToCall = Math.max(0, 16 - (currTime - lastTime)),
id = setTimeout(function() {
callback(currTime + timeToCall)
}, timeToCall);
return lastTime = currTime + timeToCall, id
}
}
var cancelAnimationFrame = window.cancelAnimationFrame;
"undefined" == typeof cancelAnimationFrame && ("undefined" != typeof webkitCancelAnimationFrame ? cancelAnimationFrame = webkitCancelAnimationFrame : "undefined" != typeof mozCancelAnimationFrame ? cancelAnimationFrame =
mozCancelAnimationFrame : "undefined" != typeof msCancelAnimationFrame ? cancelAnimationFrame = msCancelAnimationFrame : "undefined" == typeof cancelAnimationFrame && (cancelAnimationFrame = function(id) {
clearTimeout(id)
}));
var AudioContext = window.AudioContext;
"undefined" == typeof AudioContext && ("undefined" != typeof webkitAudioContext && (AudioContext = webkitAudioContext), "undefined" != typeof mozAudioContext && (AudioContext = mozAudioContext));
var URL = window.URL;
"undefined" == typeof URL && "undefined" != typeof webkitURL && (URL = webkitURL), "undefined" != typeof navigator && "undefined" == typeof navigator.getUserMedia && ("undefined" != typeof navigator.webkitGetUserMedia && (navigator
.getUserMedia = navigator.webkitGetUserMedia), "undefined" != typeof navigator.mozGetUserMedia && (navigator.getUserMedia = navigator.mozGetUserMedia));
var isEdge = !(navigator.userAgent.indexOf("Edge") === -1 || !navigator.msSaveBlob && !navigator.msSaveOrOpenBlob),
isOpera = !!window.opera || navigator.userAgent.indexOf("OPR/") !== -1,
isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1 && "netscape" in window && / rv:/.test(navigator.userAgent),
isChrome = !isOpera && !isEdge && !!navigator.webkitGetUserMedia || isElectron() || navigator.userAgent.toLowerCase().indexOf("chrome/") !== -1,
isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
isSafari && !isChrome && navigator.userAgent.indexOf("CriOS") !== -1 && (isSafari = !1, isChrome = !0);
var MediaStream = window.MediaStream;
"undefined" == typeof MediaStream && "undefined" != typeof webkitMediaStream && (MediaStream = webkitMediaStream), "undefined" != typeof MediaStream && "undefined" == typeof MediaStream.prototype.stop && (MediaStream.prototype.stop =
function() {
this.getTracks().forEach(function(track) {
track.stop()
})
}), "undefined" != typeof RecordRTC && (RecordRTC.invokeSaveAsDialog = invokeSaveAsDialog, RecordRTC.getTracks = getTracks, RecordRTC.getSeekableBlob = getSeekableBlob, RecordRTC.bytesToSize = bytesToSize, RecordRTC.isElectron =
isElectron);
var Storage = {};
"undefined" != typeof AudioContext ? Storage.AudioContext = AudioContext : "undefined" != typeof webkitAudioContext && (Storage.AudioContext = webkitAudioContext), "undefined" != typeof RecordRTC && (RecordRTC.Storage = Storage),
"undefined" != typeof RecordRTC && (RecordRTC.MediaStreamRecorder = MediaStreamRecorder), "undefined" != typeof RecordRTC && (RecordRTC.StereoAudioRecorder = StereoAudioRecorder), "undefined" != typeof RecordRTC && (RecordRTC
.CanvasRecorder = CanvasRecorder), "undefined" != typeof RecordRTC && (RecordRTC.WhammyRecorder = WhammyRecorder);
var Whammy = function() {
function WhammyVideo(duration) {
this.frames = [], this.duration = duration || 1, this.quality = .8
}
function processInWebWorker(_function) {
var blob = URL.createObjectURL(new Blob([_function.toString(), "this.onmessage = function (eee) {" + _function.name + "(eee.data);}"], {
type: "application/javascript"
})),
worker = new Worker(blob);
return URL.revokeObjectURL(blob), worker
}
function whammyInWebWorker(frames) {
function ArrayToWebM(frames) {
var info = checkFrames(frames);
if (!info) return [];
for (var clusterMaxDuration = 3e4, EBML = [{
id: 440786851,
data: [{
data: 1,
id: 17030
}, {
data: 1,
id: 17143
}, {
data: 4,
id: 17138
}, {
data: 8,
id: 17139
}, {
data: "webm",
id: 17026
}, {
data: 2,
id: 17031
}, {
data: 2,
id: 17029
}]
}, {
id: 408125543,
data: [{
id: 357149030,
data: [{
data: 1e6,
id: 2807729
}, {
data: "whammy",
id: 19840
}, {
data: "whammy",
id: 22337
}, {
data: doubleToString(info.duration),
id: 17545
}]
}, {
id: 374648427,
data: [{
id: 174,
data: [{
data: 1,
id: 215
}, {
data: 1,
id: 29637
}, {
data: 0,
id: 156
}, {
data: "und",
id: 2274716
}, {
data: "V_VP8",
id: 134
}, {
data: "VP8",
id: 2459272
}, {
data: 1,
id: 131
}, {
id: 224,
data: [{
data: info.width,
id: 176
}, {
data: info.height,
id: 186
}]
}]
}]
}]
}], frameNumber = 0, clusterTimecode = 0; frameNumber < frames.length;) {
var clusterFrames = [],
clusterDuration = 0;
do clusterFrames.push(frames[frameNumber]), clusterDuration += frames[frameNumber].duration, frameNumber++; while (frameNumber < frames.length && clusterDuration < clusterMaxDuration);
var clusterCounter = 0,
cluster = {
id: 524531317,
data: getClusterData(clusterTimecode, clusterCounter, clusterFrames)
};
EBML[1].data.push(cluster), clusterTimecode += clusterDuration
}
return generateEBML(EBML)
}
function getClusterData(clusterTimecode, clusterCounter, clusterFrames) {
return [{
data: clusterTimecode,
id: 231
}].concat(clusterFrames.map(function(webp) {
var block = makeSimpleBlock({
discardable: 0,
frame: webp.data.slice(4),
invisible: 0,
keyframe: 1,
lacing: 0,
trackNum: 1,
timecode: Math.round(clusterCounter)
});
return clusterCounter += webp.duration, {
data: block,
id: 163
}
}))
}
function checkFrames(frames) {
if (!frames[0]) return void postMessage({
error: "Something went wrong. Maybe WebP format is not supported in the current browser."
});
for (var width = frames[0].width, height = frames[0].height, duration = frames[0].duration, i = 1; i < frames.length; i++) duration += frames[i].duration;
return {
duration: duration,
width: width,
height: height
}
}
function numToBuffer(num) {
for (var parts = []; num > 0;) parts.push(255 & num), num >>= 8;
return new Uint8Array(parts.reverse())
}
function strToBuffer(str) {
return new Uint8Array(str.split("").map(function(e) {
return e.charCodeAt(0)
}))
}
function bitsToBuffer(bits) {
var data = [],
pad = bits.length % 8 ? new Array(9 - bits.length % 8).join("0") : "";
bits = pad + bits;
for (var i = 0; i < bits.length; i += 8) data.push(parseInt(bits.substr(i, 8), 2));
return new Uint8Array(data)
}
function generateEBML(json) {
for (var ebml = [], i = 0; i < json.length; i++) {
var data = json[i].data;
"object" == typeof data && (data = generateEBML(data)), "number" == typeof data && (data = bitsToBuffer(data.toString(2))), "string" == typeof data && (data = strToBuffer(data));
var len = data.size || data.byteLength || data.length,
zeroes = Math.ceil(Math.ceil(Math.log(len) / Math.log(2)) / 8),
sizeToString = len.toString(2),
padded = new Array(7 * zeroes + 7 + 1 - sizeToString.length).join("0") + sizeToString,
size = new Array(zeroes).join("0") + "1" + padded;
ebml.push(numToBuffer(json[i].id)), ebml.push(bitsToBuffer(size)), ebml.push(data)
}
return new Blob(ebml, {
type: "video/webm"
})
}
function makeSimpleBlock(data) {
var flags = 0;
if (data.keyframe && (flags |= 128), data.invisible && (flags |= 8), data.lacing && (flags |= data.lacing << 1), data.discardable && (flags |= 1), data.trackNum > 127) throw "TrackNumber > 127 not supported";
var out = [128 | data.trackNum, data.timecode >> 8, 255 & data.timecode, flags].map(function(e) {
return String.fromCharCode(e)
}).join("") + data.frame;
return out
}
function parseWebP(riff) {
for (var VP8 = riff.RIFF[0].WEBP[0], frameStart = VP8.indexOf("*"), i = 0, c = []; i < 4; i++) c[i] = VP8.charCodeAt(frameStart + 3 + i);
var width, height, tmp;
return tmp = c[1] << 8 | c[0], width = 16383 & tmp, tmp = c[3] << 8 | c[2], height = 16383 & tmp, {
width: width,
height: height,
data: VP8,
riff: riff
}
}
function getStrLength(string, offset) {
return parseInt(string.substr(offset + 4, 4).split("").map(function(i) {
var unpadded = i.charCodeAt(0).toString(2);
return new Array(8 - unpadded.length + 1).join("0") + unpadded
}).join(""), 2)
}
function parseRIFF(string) {
for (var offset = 0, chunks = {}; offset < string.length;) {
var id = string.substr(offset, 4),
len = getStrLength(string, offset),
data = string.substr(offset + 4 + 4, len);
offset += 8 + len, chunks[id] = chunks[id] || [], "RIFF" === id || "LIST" === id ? chunks[id].push(parseRIFF(data)) : chunks[id].push(data)
}
return chunks
}
function doubleToString(num) {
return [].slice.call(new Uint8Array(new Float64Array([num]).buffer), 0).map(function(e) {
return String.fromCharCode(e)
}).reverse().join("")
}
var webm = new ArrayToWebM(frames.map(function(frame) {
var webp = parseWebP(parseRIFF(atob(frame.image.slice(23))));
return webp.duration = frame.duration, webp
}));
postMessage(webm)
}
return WhammyVideo.prototype.add = function(frame, duration) {
if ("canvas" in frame && (frame = frame.canvas), "toDataURL" in frame && (frame = frame.toDataURL("image/webp", this.quality)), !/^data:image\/webp;base64,/gi.test(frame))
throw "Input must be formatted properly as a base64 encoded DataURI of type image/webp";
this.frames.push({
image: frame,
duration: duration || this.duration
})
}, WhammyVideo.prototype.compile = function(callback) {
var webWorker = processInWebWorker(whammyInWebWorker);
webWorker.onmessage = function(event) {
return event.data.error ? void console.error(event.data.error) : void callback(event.data)
}, webWorker.postMessage(this.frames)
}, {
Video: WhammyVideo
}
}();
"undefined" != typeof RecordRTC && (RecordRTC.Whammy = Whammy);
var DiskStorage = {
init: function() {
function createObjectStore(dataBase) {
dataBase.createObjectStore(self.dataStoreName)
}
function putInDB() {
function getFromStore(portionName) {
transaction.objectStore(self.dataStoreName).get(portionName).onsuccess = function(event) {
self.callback && self.callback(event.target.result, portionName)
}
}
var transaction = db.transaction([self.dataStoreName], "readwrite");
self.videoBlob && transaction.objectStore(self.dataStoreName).put(self.videoBlob, "videoBlob"), self.gifBlob && transaction.objectStore(self.dataStoreName).put(self.gifBlob, "gifBlob"), self.audioBlob && transaction.objectStore(self
.dataStoreName).put(self.audioBlob, "audioBlob"), getFromStore("audioBlob"), getFromStore("videoBlob"), getFromStore("gifBlob")
}
var self = this;
if ("undefined" == typeof indexedDB || "undefined" == typeof indexedDB.open) return void console.error("IndexedDB API are not available in this browser.");
var db, dbVersion = 1,
dbName = this.dbName || location.href.replace(/\/|:|#|%|\.|\[|\]/g, ""),
request = indexedDB.open(dbName, dbVersion);
request.onerror = self.onError, request.onsuccess = function() {
if (db = request.result, db.onerror = self.onError, db.setVersion)
if (db.version !== dbVersion) {
var setVersion = db.setVersion(dbVersion);
setVersion.onsuccess = function() {
createObjectStore(db), putInDB()
}
} else putInDB();
else putInDB()
}, request.onupgradeneeded = function(event) {
createObjectStore(event.target.result)
}
},
Fetch: function(callback) {
return this.callback = callback, this.init(), this
},
Store: function(config) {
return this.audioBlob = config.audioBlob, this.videoBlob = config.videoBlob, this.gifBlob = config.gifBlob, this.init(), this
},
onError: function(error) {
console.error(JSON.stringify(error, null, "\t"))
},
dataStoreName: "recordRTC",
dbName: null
};
"undefined" != typeof RecordRTC && (RecordRTC.DiskStorage = DiskStorage), "undefined" != typeof RecordRTC && (RecordRTC.GifRecorder = GifRecorder), "undefined" == typeof RecordRTC && ("undefined" != typeof module && (module.exports =
MultiStreamsMixer), "function" == typeof define && define.amd && define("MultiStreamsMixer", [], function() {
return MultiStreamsMixer
})), "undefined" != typeof RecordRTC && (RecordRTC.MultiStreamRecorder = MultiStreamRecorder), "undefined" != typeof RecordRTC && (RecordRTC.RecordRTCPromisesHandler = RecordRTCPromisesHandler), "undefined" != typeof RecordRTC && (RecordRTC
.WebAssemblyRecorder = WebAssemblyRecorder);
'use strict';
// Last Updated On: 2020-08-12 11:18:41 AM UTC
// ________________
// DetectRTC v1.4.1
// Open-Sourced: https://github.com/muaz-khan/DetectRTC
// --------------------------------------------------
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// --------------------------------------------------
"use strict";
! function() {
function getBrowserInfo() {
var nameOffset, verOffset, ix, nAgt = (navigator.appVersion, navigator.userAgent),
browserName = navigator.appName,
fullVersion = "" + parseFloat(navigator.appVersion),
majorVersion = parseInt(navigator.appVersion, 10);
if (isOpera) {
browserName = "Opera";
try {
fullVersion = navigator.userAgent.split("OPR/")[1].split(" ")[0], majorVersion = fullVersion.split(".")[0]
} catch (e) {
fullVersion = "0.0.0.0", majorVersion = 0
}
} else isIE ? (verOffset = nAgt.indexOf("rv:"), verOffset > 0 ? fullVersion = nAgt.substring(verOffset + 3) : (verOffset = nAgt.indexOf("MSIE"), fullVersion = nAgt.substring(verOffset + 5)), browserName = "IE") : isChrome ? (verOffset =
nAgt.indexOf("Chrome"), browserName = "Chrome", fullVersion = nAgt.substring(verOffset + 7)) : isSafari ? nAgt.indexOf("CriOS") !== -1 ? (verOffset = nAgt.indexOf("CriOS"), browserName = "Chrome", fullVersion = nAgt.substring(
verOffset + 6)) : nAgt.indexOf("FxiOS") !== -1 ? (verOffset = nAgt.indexOf("FxiOS"), browserName = "Firefox", fullVersion = nAgt.substring(verOffset + 6)) : (verOffset = nAgt.indexOf("Safari"), browserName = "Safari", fullVersion =
nAgt.substring(verOffset + 7), (verOffset = nAgt.indexOf("Version")) !== -1 && (fullVersion = nAgt.substring(verOffset + 8)), navigator.userAgent.indexOf("Version/") !== -1 && (fullVersion = navigator.userAgent.split("Version/")[1]
.split(" ")[0])) : isFirefox ? (verOffset = nAgt.indexOf("Firefox"), browserName = "Firefox", fullVersion = nAgt.substring(verOffset + 8)) : (nameOffset = nAgt.lastIndexOf(" ") + 1) < (verOffset = nAgt.lastIndexOf("/")) && (
browserName = nAgt.substring(nameOffset, verOffset), fullVersion = nAgt.substring(verOffset + 1), browserName.toLowerCase() === browserName.toUpperCase() && (browserName = navigator.appName));
return isEdge && (browserName = "Edge", fullVersion = navigator.userAgent.split("Edge/")[1]), (ix = fullVersion.search(/[; \)]/)) !== -1 && (fullVersion = fullVersion.substring(0, ix)), majorVersion = parseInt("" + fullVersion, 10),
isNaN(majorVersion) && (fullVersion = "" + parseFloat(navigator.appVersion), majorVersion = parseInt(navigator.appVersion, 10)), {
fullVersion: fullVersion,
version: majorVersion,
name: browserName,
isPrivateBrowsing: !1
}
}
function retry(isDone, next) {
var currentTrial = 0,
maxRetry = 50,
isTimeout = !1,
id = window.setInterval(function() {
isDone() && (window.clearInterval(id), next(isTimeout)), currentTrial++ > maxRetry && (window.clearInterval(id), isTimeout = !0, next(isTimeout))
}, 10)
}
function isIE10OrLater(userAgent) {
var ua = userAgent.toLowerCase();
if (0 === ua.indexOf("msie") && 0 === ua.indexOf("trident")) return !1;
var match = /(?:msie|rv:)\s?([\d\.]+)/.exec(ua);
return !!(match && parseInt(match[1], 10) >= 10)
}
function detectPrivateMode(callback) {
var isPrivate;
try {
if (window.webkitRequestFileSystem) window.webkitRequestFileSystem(window.TEMPORARY, 1, function() {
isPrivate = !1
}, function(e) {
isPrivate = !0
});
else if (window.indexedDB && /Firefox/.test(window.navigator.userAgent)) {
var db;
try {
db = window.indexedDB.open("test"), db.onerror = function() {
return !0
}
} catch (e) {
isPrivate = !0
}
"undefined" == typeof isPrivate && retry(function() {
return "done" === db.readyState
}, function(isTimeout) {
isTimeout || (isPrivate = !db.result)
})
} else if (isIE10OrLater(window.navigator.userAgent)) {
isPrivate = !1;
try {
window.indexedDB || (isPrivate = !0)
} catch (e) {
isPrivate = !0
}
} else if (window.localStorage && /Safari/.test(window.navigator.userAgent)) {
try {
window.localStorage.setItem("test", 1)
} catch (e) {
isPrivate = !0
}
"undefined" == typeof isPrivate && (isPrivate = !1, window.localStorage.removeItem("test"))
}
} catch (e) {
isPrivate = !1
}
retry(function() {
return "undefined" != typeof isPrivate
}, function(isTimeout) {
callback(isPrivate)
})
}
function detectDesktopOS() {
for (var cs, unknown = "-", nVer = navigator.appVersion, nAgt = navigator.userAgent, os = unknown, clientStrings = [{
s: "Chrome OS",
r: /CrOS/
}, {
s: "Windows 10",
r: /(Windows 10.0|Windows NT 10.0)/
}, {
s: "Windows 8.1",
r: /(Windows 8.1|Windows NT 6.3)/
}, {
s: "Windows 8",
r: /(Windows 8|Windows NT 6.2)/
}, {
s: "Windows 7",
r: /(Windows 7|Windows NT 6.1)/
}, {
s: "Windows Vista",
r: /Windows NT 6.0/
}, {
s: "Windows Server 2003",
r: /Windows NT 5.2/
}, {
s: "Windows XP",
r: /(Windows NT 5.1|Windows XP)/
}, {
s: "Windows 2000",
r: /(Windows NT 5.0|Windows 2000)/
}, {
s: "Windows ME",
r: /(Win 9x 4.90|Windows ME)/
}, {
s: "Windows 98",
r: /(Windows 98|Win98)/
}, {
s: "Windows 95",
r: /(Windows 95|Win95|Windows_95)/
}, {
s: "Windows NT 4.0",
r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/
}, {
s: "Windows CE",
r: /Windows CE/
}, {
s: "Windows 3.11",
r: /Win16/
}, {
s: "Android",
r: /Android/
}, {
s: "Open BSD",
r: /OpenBSD/
}, {
s: "Sun OS",
r: /SunOS/
}, {
s: "Linux",
r: /(Linux|X11)/
}, {
s: "iOS",
r: /(iPhone|iPad|iPod)/
}, {
s: "Mac OS X",
r: /Mac OS X/
}, {
s: "Mac OS",
r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/
}, {
s: "QNX",
r: /QNX/
}, {
s: "UNIX",
r: /UNIX/
}, {
s: "BeOS",
r: /BeOS/
}, {
s: "OS/2",
r: /OS\/2/
}, {
s: "Search Bot",
r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/
}], i = 0; cs = clientStrings[i]; i++)
if (cs.r.test(nAgt)) {
os = cs.s;
break
} var osVersion = unknown;
switch (/Windows/.test(os) && (/Windows (.*)/.test(os) && (osVersion = /Windows (.*)/.exec(os)[1]), os = "Windows"), os) {
case "Mac OS X":
/Mac OS X (10[\.\_\d]+)/.test(nAgt) && (osVersion = /Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1]);
break;
case "Android":
/Android ([\.\_\d]+)/.test(nAgt) && (osVersion = /Android ([\.\_\d]+)/.exec(nAgt)[1]);
break;
case "iOS":
/OS (\d+)_(\d+)_?(\d+)?/.test(nAgt) && (osVersion = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer), osVersion && osVersion.length > 3 && (osVersion = osVersion[1] + "." + osVersion[2] + "." + (0 | osVersion[3])))
}
return {
osName: os,
osVersion: osVersion
}
}
function getAndroidVersion(ua) {
ua = (ua || navigator.userAgent).toLowerCase();
var match = ua.match(/android\s([0-9\.]*)/);
return !!match && match[1]
}
function DetectLocalIPAddress(callback, stream) {
if (DetectRTC.isWebRTCSupported) {
var isPublic = !0,
isIpv4 = !0;
getIPs(function(ip) {
ip ? ip.match(regexIpv4Local) ? (isPublic = !1, callback("Local: " + ip, isPublic, isIpv4)) : ip.match(regexIpv6) ? (isIpv4 = !1, callback("Public: " + ip, isPublic, isIpv4)) : callback("Public: " + ip, isPublic, isIpv4) :
callback()
}, stream)
}
}
function getIPs(callback, stream) {
function handleCandidate(candidate) {
if (!candidate) return void callback();
var match = regexIpv4.exec(candidate);
if (match) {
var ipAddress = match[1],
isPublic = candidate.match(regexIpv4Local),
isIpv4 = !0;
void 0 === ipDuplicates[ipAddress] && callback(ipAddress, isPublic, isIpv4), ipDuplicates[ipAddress] = !0
}
}
function afterCreateOffer() {
var lines = pc.localDescription.sdp.split("\n");
lines.forEach(function(line) {
line && 0 === line.indexOf("a=candidate:") && handleCandidate(line)
})
}
if ("undefined" != typeof document && "function" == typeof document.getElementById) {
var ipDuplicates = {},
RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
if (!RTCPeerConnection) {
var iframe = document.getElementById("iframe");
if (!iframe) return;
var win = iframe.contentWindow;
RTCPeerConnection = win.RTCPeerConnection || win.mozRTCPeerConnection || win.webkitRTCPeerConnection
}
if (RTCPeerConnection) {
var peerConfig = null;
"Chrome" === DetectRTC.browser && DetectRTC.browser.version < 58 && (peerConfig = {
optional: [{
RtpDataChannels: !0
}]
});
var servers = {
iceServers: [{
urls: "stun:stun.l.google.com:19302"
}]
},
pc = new RTCPeerConnection(servers, peerConfig);
if (stream && (pc.addStream ? pc.addStream(stream) : pc.addTrack && stream.getTracks()[0] && pc.addTrack(stream.getTracks()[0], stream)), pc.onicecandidate = function(event) {
event.candidate && event.candidate.candidate ? handleCandidate(event.candidate.candidate) : handleCandidate()
}, !stream) try {
pc.createDataChannel("sctp", {})
} catch (e) {}
DetectRTC.isPromisesSupported ? pc.createOffer().then(function(result) {
pc.setLocalDescription(result).then(afterCreateOffer)
}) : pc.createOffer(function(result) {
pc.setLocalDescription(result, afterCreateOffer, function() {})
}, function() {})
}
}
}
function checkDeviceSupport(callback) {
if (!canEnumerate) return void(callback && callback());
if (!navigator.enumerateDevices && window.MediaStreamTrack && window.MediaStreamTrack.getSources && (navigator.enumerateDevices = window.MediaStreamTrack.getSources.bind(window.MediaStreamTrack)), !navigator.enumerateDevices && navigator
.enumerateDevices && (navigator.enumerateDevices = navigator.enumerateDevices.bind(navigator)), !navigator.enumerateDevices) return void(callback && callback());
MediaDevices = [], audioInputDevices = [], audioOutputDevices = [], videoInputDevices = [], hasMicrophone = !1, hasSpeakers = !1, hasWebcam = !1, isWebsiteHasMicrophonePermissions = !1, isWebsiteHasWebcamPermissions = !1;
var alreadyUsedDevices = {};
navigator.enumerateDevices(function(devices) {
MediaDevices = [], audioInputDevices = [], audioOutputDevices = [], videoInputDevices = [], devices.forEach(function(_device) {
var device = {};
for (var d in _device) try {
"function" != typeof _device[d] && (device[d] = _device[d])
} catch (e) {}
alreadyUsedDevices[device.deviceId + device.label + device.kind] || ("audio" === device.kind && (device.kind = "audioinput"), "video" === device.kind && (device.kind = "videoinput"), device.deviceId || (device.deviceId =
device.id), device.id || (device.id = device.deviceId), device.label ? ("videoinput" !== device.kind || isWebsiteHasWebcamPermissions || (isWebsiteHasWebcamPermissions = !0), "audioinput" !== device.kind ||
isWebsiteHasMicrophonePermissions || (isWebsiteHasMicrophonePermissions = !0)) : (device.isCustomLabel = !0, "videoinput" === device.kind ? device.label = "Camera " + (videoInputDevices.length + 1) : "audioinput" ===
device.kind ? device.label = "Microphone " + (audioInputDevices.length + 1) : "audiooutput" === device.kind ? device.label = "Speaker " + (audioOutputDevices.length + 1) : device.label =
"Please invoke getUserMedia once.", "undefined" != typeof DetectRTC && DetectRTC.browser.isChrome && DetectRTC.browser.version >= 46 && !/^(https:|chrome-extension:)$/g.test(location.protocol || "") && "undefined" !=
typeof document && "string" == typeof document.domain && document.domain.search && document.domain.search(/localhost|127.0./g) === -1 && (device.label = "HTTPs is required to get label of this " + device.kind +
" device.")), "audioinput" === device.kind && (hasMicrophone = !0, audioInputDevices.indexOf(device) === -1 && audioInputDevices.push(device)), "audiooutput" === device.kind && (hasSpeakers = !0, audioOutputDevices
.indexOf(device) === -1 && audioOutputDevices.push(device)), "videoinput" === device.kind && (hasWebcam = !0, videoInputDevices.indexOf(device) === -1 && videoInputDevices.push(device)), MediaDevices.push(device),
alreadyUsedDevices[device.deviceId + device.label + device.kind] = device)
}), "undefined" != typeof DetectRTC && (DetectRTC.MediaDevices = MediaDevices, DetectRTC.hasMicrophone = hasMicrophone, DetectRTC.hasSpeakers = hasSpeakers, DetectRTC.hasWebcam = hasWebcam, DetectRTC
.isWebsiteHasWebcamPermissions = isWebsiteHasWebcamPermissions, DetectRTC.isWebsiteHasMicrophonePermissions = isWebsiteHasMicrophonePermissions, DetectRTC.audioInputDevices = audioInputDevices, DetectRTC.audioOutputDevices =
audioOutputDevices, DetectRTC.videoInputDevices = videoInputDevices), callback && callback()
})
}
function getAspectRatio(w, h) {
function gcd(a, b) {
return 0 == b ? a : gcd(b, a % b)
}
var r = gcd(w, h);
return w / r / (h / r)
}
var browserFakeUserAgent = "Fake/5.0 (FakeOS) AppleWebKit/123 (KHTML, like Gecko) Fake/12.3.4567.89 Fake/123.45",
isNodejs = "object" == typeof process && "object" == typeof process.versions && process.versions.node && !process.browser;
if (isNodejs) {
var version = process.versions.node.toString().replace("v", "");
browserFakeUserAgent = "Nodejs/" + version + " (NodeOS) AppleWebKit/" + version + " (KHTML, like Gecko) Nodejs/" + version + " Nodejs/" + version
}! function(that) {
"undefined" == typeof window && ("undefined" == typeof window && "undefined" != typeof global ? (global.navigator = {
userAgent: browserFakeUserAgent,
getUserMedia: function() {}
}, that.window = global) : "undefined" == typeof window, "undefined" == typeof location && (that.location = {
protocol: "file:",
href: "",
hash: ""
}), "undefined" == typeof screen && (that.screen = {
width: 0,
height: 0
}))
}("undefined" != typeof global ? global : window);
var navigator = window.navigator;
"undefined" != typeof navigator ? ("undefined" != typeof navigator.webkitGetUserMedia && (navigator.getUserMedia = navigator.webkitGetUserMedia), "undefined" != typeof navigator.mozGetUserMedia && (navigator.getUserMedia = navigator
.mozGetUserMedia)) : navigator = {
getUserMedia: function() {},
userAgent: browserFakeUserAgent
};
var isMobileDevice = !!/Android|webOS|iPhone|iPad|iPod|BB10|BlackBerry|IEMobile|Opera Mini|Mobile|mobile/i.test(navigator.userAgent || ""),
isEdge = !(navigator.userAgent.indexOf("Edge") === -1 || !navigator.msSaveOrOpenBlob && !navigator.msSaveBlob),
isOpera = !!window.opera || navigator.userAgent.indexOf(" OPR/") >= 0,
isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1 && "netscape" in window && / rv:/.test(navigator.userAgent),
isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent),
isChrome = !!window.chrome && !isOpera,
isIE = "undefined" != typeof document && !!document.documentMode && !isEdge,
isMobile = {
Android: function() {
return navigator.userAgent.match(/Android/i)
},
BlackBerry: function() {
return navigator.userAgent.match(/BlackBerry|BB10/i)
},
iOS: function() {
return navigator.userAgent.match(/iPhone|iPad|iPod/i)
},
Opera: function() {
return navigator.userAgent.match(/Opera Mini/i)
},
Windows: function() {
return navigator.userAgent.match(/IEMobile/i)
},
any: function() {
return isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Opera() || isMobile.Windows()
},
getOsName: function() {
var osName = "Unknown OS";
return isMobile.Android() && (osName = "Android"), isMobile.BlackBerry() && (osName = "BlackBerry"), isMobile.iOS() && (osName = "iOS"), isMobile.Opera() && (osName = "Opera Mini"), isMobile.Windows() && (osName = "Windows"), osName
}
},
osName = "Unknown OS",
osVersion = "Unknown OS Version",
osInfo = detectDesktopOS();
osInfo && osInfo.osName && "-" != osInfo.osName ? (osName = osInfo.osName, osVersion = osInfo.osVersion) : isMobile.any() && (osName = isMobile.getOsName(), "Android" == osName && (osVersion = getAndroidVersion()));
var isNodejs = "object" == typeof process && "object" == typeof process.versions && process.versions.node;
"Unknown OS" === osName && isNodejs && (osName = "Nodejs", osVersion = process.versions.node.toString().replace("v", ""));
var isCanvasSupportsStreamCapturing = !1,
isVideoSupportsStreamCapturing = !1;
["captureStream", "mozCaptureStream", "webkitCaptureStream"].forEach(function(item) {
"undefined" != typeof document && "function" == typeof document.createElement && (!isCanvasSupportsStreamCapturing && item in document.createElement("canvas") && (isCanvasSupportsStreamCapturing = !0), !
isVideoSupportsStreamCapturing && item in document.createElement("video") && (isVideoSupportsStreamCapturing = !0))
});
var regexIpv4Local = /^(192\.168\.|169\.254\.|10\.|172\.(1[6-9]|2\d|3[01]))/,
regexIpv4 = /([0-9]{1,3}(\.[0-9]{1,3}){3})/,
regexIpv6 = /[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7}/,
MediaDevices = [],
audioInputDevices = [],
audioOutputDevices = [],
videoInputDevices = [];
navigator.mediaDevices && navigator.mediaDevices.enumerateDevices && (navigator.enumerateDevices = function(callback) {
var enumerateDevices = navigator.mediaDevices.enumerateDevices();
enumerateDevices && enumerateDevices.then ? navigator.mediaDevices.enumerateDevices().then(callback)["catch"](function() {
callback([])
}) : callback([])
});
var canEnumerate = !1;
"undefined" != typeof MediaStreamTrack && "getSources" in MediaStreamTrack ? canEnumerate = !0 : navigator.mediaDevices && navigator.mediaDevices.enumerateDevices && (canEnumerate = !0);
var hasMicrophone = !1,
hasSpeakers = !1,
hasWebcam = !1,
isWebsiteHasMicrophonePermissions = !1,
isWebsiteHasWebcamPermissions = !1,
DetectRTC = window.DetectRTC || {};
DetectRTC.browser = getBrowserInfo(), detectPrivateMode(function(isPrivateBrowsing) {
DetectRTC.browser.isPrivateBrowsing = !!isPrivateBrowsing
}), DetectRTC.browser["is" + DetectRTC.browser.name] = !0, DetectRTC.osName = osName, DetectRTC.osVersion = osVersion;
var isWebRTCSupported = ("object" == typeof process && "object" == typeof process.versions && process.versions["node-webkit"], !1);
["RTCPeerConnection", "webkitRTCPeerConnection", "mozRTCPeerConnection", "RTCIceGatherer"].forEach(function(item) {
isWebRTCSupported || item in window && (isWebRTCSupported = !0)
}), DetectRTC.isWebRTCSupported = isWebRTCSupported, DetectRTC.isORTCSupported = "undefined" != typeof RTCIceGatherer;
var isScreenCapturingSupported = !1;
if (DetectRTC.browser.isChrome && DetectRTC.browser.version >= 35 ? isScreenCapturingSupported = !0 : DetectRTC.browser.isFirefox && DetectRTC.browser.version >= 34 ? isScreenCapturingSupported = !0 : DetectRTC.browser.isEdge && DetectRTC
.browser.version >= 17 ? isScreenCapturingSupported = !0 : "Android" === DetectRTC.osName && DetectRTC.browser.isChrome && (isScreenCapturingSupported = !0), (navigator.getDisplayMedia || navigator.mediaDevices && navigator.mediaDevices
.getDisplayMedia) && (isScreenCapturingSupported = !0), !/^(https:|chrome-extension:)$/g.test(location.protocol || "")) {
var isNonLocalHost = "undefined" != typeof document && "string" == typeof document.domain && document.domain.search && document.domain.search(/localhost|127.0./g) === -1;
isNonLocalHost && (DetectRTC.browser.isChrome || DetectRTC.browser.isEdge || DetectRTC.browser.isOpera) ? isScreenCapturingSupported = !1 : DetectRTC.browser.isFirefox && (isScreenCapturingSupported = !1)
}
DetectRTC.isScreenCapturingSupported = isScreenCapturingSupported;
var webAudio = {
isSupported: !1,
isCreateMediaStreamSourceSupported: !1
};
["AudioContext", "webkitAudioContext", "mozAudioContext", "msAudioContext"].forEach(function(item) {
webAudio.isSupported || item in window && (webAudio.isSupported = !0, window[item] && "createMediaStreamSource" in window[item].prototype && (webAudio.isCreateMediaStreamSourceSupported = !0))
}), DetectRTC.isAudioContextSupported = webAudio.isSupported, DetectRTC.isCreateMediaStreamSourceSupported = webAudio.isCreateMediaStreamSourceSupported;
var isRtpDataChannelsSupported = !1;
DetectRTC.browser.isChrome && DetectRTC.browser.version > 31 && (isRtpDataChannelsSupported = !0), DetectRTC.isRtpDataChannelsSupported = isRtpDataChannelsSupported;
var isSCTPSupportd = !1;
DetectRTC.browser.isFirefox && DetectRTC.browser.version > 28 ? isSCTPSupportd = !0 : DetectRTC.browser.isChrome && DetectRTC.browser.version > 25 ? isSCTPSupportd = !0 : DetectRTC.browser.isOpera && DetectRTC.browser.version >= 11 && (
isSCTPSupportd = !0), DetectRTC.isSctpDataChannelsSupported = isSCTPSupportd, DetectRTC.isMobileDevice = isMobileDevice;
var isGetUserMediaSupported = !1;
navigator.getUserMedia ? isGetUserMediaSupported = !0 : navigator.mediaDevices && navigator.mediaDevices.getUserMedia && (isGetUserMediaSupported = !0), DetectRTC.browser.isChrome && DetectRTC.browser.version >= 46 && !
/^(https:|chrome-extension:)$/g.test(location.protocol || "") && "undefined" != typeof document && "string" == typeof document.domain && document.domain.search && document.domain.search(/localhost|127.0./g) === -1 && (
isGetUserMediaSupported = "Requires HTTPs"), "Nodejs" === DetectRTC.osName && (isGetUserMediaSupported = !1), DetectRTC.isGetUserMediaSupported = isGetUserMediaSupported;
var displayResolution = "";
if (screen.width) {
var width = screen.width ? screen.width : "",
height = screen.height ? screen.height : "";
displayResolution += "" + width + " x " + height
}
DetectRTC.displayResolution = displayResolution, DetectRTC.displayAspectRatio = getAspectRatio(screen.width, screen.height).toFixed(2), DetectRTC.isCanvasSupportsStreamCapturing = isCanvasSupportsStreamCapturing, DetectRTC
.isVideoSupportsStreamCapturing = isVideoSupportsStreamCapturing, "Chrome" == DetectRTC.browser.name && DetectRTC.browser.version >= 53 && (DetectRTC.isCanvasSupportsStreamCapturing || (DetectRTC.isCanvasSupportsStreamCapturing =
"Requires chrome flag: enable-experimental-web-platform-features"), DetectRTC.isVideoSupportsStreamCapturing || (DetectRTC.isVideoSupportsStreamCapturing = "Requires chrome flag: enable-experimental-web-platform-features")), DetectRTC
.DetectLocalIPAddress = DetectLocalIPAddress, DetectRTC.isWebSocketsSupported = "WebSocket" in window && 2 === window.WebSocket.CLOSING, DetectRTC.isWebSocketsBlocked = !DetectRTC.isWebSocketsSupported, "Nodejs" === DetectRTC.osName && (
DetectRTC.isWebSocketsSupported = !0, DetectRTC.isWebSocketsBlocked = !1), DetectRTC.checkWebSocketsSupport = function(callback) {
callback = callback || function() {};
try {
var starttime, websocket = new WebSocket("wss://echo.websocket.org:443/");
websocket.onopen = function() {
DetectRTC.isWebSocketsBlocked = !1, starttime = (new Date).getTime(), websocket.send("ping")
}, websocket.onmessage = function() {
DetectRTC.WebsocketLatency = (new Date).getTime() - starttime + "ms", callback(), websocket.close(), websocket = null
}, websocket.onerror = function() {
DetectRTC.isWebSocketsBlocked = !0, callback()
}
} catch (e) {
DetectRTC.isWebSocketsBlocked = !0, callback()
}
}, DetectRTC.load = function(callback) {
callback = callback || function() {}, checkDeviceSupport(callback)
}, "undefined" != typeof MediaDevices ? DetectRTC.MediaDevices = MediaDevices : DetectRTC.MediaDevices = [], DetectRTC.hasMicrophone = hasMicrophone, DetectRTC.hasSpeakers = hasSpeakers, DetectRTC.hasWebcam = hasWebcam, DetectRTC
.isWebsiteHasWebcamPermissions = isWebsiteHasWebcamPermissions, DetectRTC.isWebsiteHasMicrophonePermissions = isWebsiteHasMicrophonePermissions, DetectRTC.audioInputDevices = audioInputDevices, DetectRTC.audioOutputDevices =
audioOutputDevices, DetectRTC.videoInputDevices = videoInputDevices;
var isSetSinkIdSupported = !1;
"undefined" != typeof document && "function" == typeof document.createElement && "setSinkId" in document.createElement("video") && (isSetSinkIdSupported = !0), DetectRTC.isSetSinkIdSupported = isSetSinkIdSupported;
var isRTPSenderReplaceTracksSupported = !1;
DetectRTC.browser.isFirefox && "undefined" != typeof mozRTCPeerConnection ? "getSenders" in mozRTCPeerConnection.prototype && (isRTPSenderReplaceTracksSupported = !0) : DetectRTC.browser.isChrome && "undefined" !=
typeof webkitRTCPeerConnection && "getSenders" in webkitRTCPeerConnection.prototype && (isRTPSenderReplaceTracksSupported = !0), DetectRTC.isRTPSenderReplaceTracksSupported = isRTPSenderReplaceTracksSupported;
var isRemoteStreamProcessingSupported = !1;
DetectRTC.browser.isFirefox && DetectRTC.browser.version > 38 && (isRemoteStreamProcessingSupported = !0), DetectRTC.isRemoteStreamProcessingSupported = isRemoteStreamProcessingSupported;
var isApplyConstraintsSupported = !1;
"undefined" != typeof MediaStreamTrack && "applyConstraints" in MediaStreamTrack.prototype && (isApplyConstraintsSupported = !0), DetectRTC.isApplyConstraintsSupported = isApplyConstraintsSupported;
var isMultiMonitorScreenCapturingSupported = !1;
DetectRTC.browser.isFirefox && DetectRTC.browser.version >= 43 && (isMultiMonitorScreenCapturingSupported = !0), DetectRTC.isMultiMonitorScreenCapturingSupported = isMultiMonitorScreenCapturingSupported, DetectRTC.isPromisesSupported = !!(
"Promise" in window), DetectRTC.version = "1.4.1", "undefined" == typeof DetectRTC && (window.DetectRTC = {});
var MediaStream = window.MediaStream;
"undefined" == typeof MediaStream && "undefined" != typeof webkitMediaStream && (MediaStream = webkitMediaStream), "undefined" != typeof MediaStream && "function" == typeof MediaStream ? DetectRTC.MediaStream = Object.keys(MediaStream
.prototype) : DetectRTC.MediaStream = !1, "undefined" != typeof MediaStreamTrack ? DetectRTC.MediaStreamTrack = Object.keys(MediaStreamTrack.prototype) : DetectRTC.MediaStreamTrack = !1;
var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
"undefined" != typeof RTCPeerConnection ? DetectRTC.RTCPeerConnection = Object.keys(RTCPeerConnection.prototype) : DetectRTC.RTCPeerConnection = !1, window.DetectRTC = DetectRTC, "undefined" != typeof module && (module.exports = DetectRTC),
"function" == typeof define && define.amd && define("DetectRTC", [], function() {
return DetectRTC
})
}();
</script>
<script>
//
// Is there anything preventing a recording?
DetectRTC.load(function() {
var has_rtc = DetectRTC.isWebRTCSupported;
var has_webcam = DetectRTC.hasWebcam;
var has_mic = DetectRTC.hasMicrophone;
if ((has_webcam === false && has_mic === false) || has_rtc === false) {
//
// We are unable to record at all.
$('#message_record_container').addClass('hidden');
} else {
//
// We support at least one of the recording features.
$('#message_record_container').removeClass('hidden');
if (has_webcam === false) {
$('#record_video_audio').addClass('hidden');
}
if (has_mic === false) {
$('#record_audio').addClass('hidden');
}
}
});
//
// Now our actual recording.
const recording = document.querySelector('#recording_element');
let recorder = null;
var type;
var visualiser_timer;
var animation_duration = 2;
var visualiser_count = 0;
var animation_done = false;
document.getElementById('record_audio').addEventListener("click", function() {
type = 'audio';
startRecording('audio');
});
document.getElementById('record_video_audio').addEventListener("click", function() {
type = 'video';
startRecording('video');
});
function visualiserCount() {
visualiser_timer = setInterval(function() {
if (visualiser_count < animation_duration) {
visualiser_count++;
} else {
if (animation_done == false) {
visualiser_count = 0;
}
}
}, 1000)
}
function BestFormat(format_wanted) {
if (format_wanted == 'video') {
var webm_support = MediaRecorder.isTypeSupported('video/webm');
var mp4_support = MediaRecorder.isTypeSupported('video/mp4');
var format = 'video/webm';
format = (mp4_support === true) ? 'video/mp4' : format;
format = (webm_support === true) ? 'video/webm' : format;
} else {
var webm_support = MediaRecorder.isTypeSupported('audio/webm');
var mp4_support = MediaRecorder.isTypeSupported('audio/mp4');
var format = 'audio/webm';
format = (mp4_support === true) ? 'audio/mp4' : format;
format = (webm_support === true) ? 'audio/webm' : format;
}
return format;
}
async function startRecording() {
//
// Hide our main controls.
// $('#record_video_audio').addClass('hidden');
// $('#record_audio').addClass('hidden');
//
// What type of recording are we doing?
$('.recording-preview-text').addClass("hidden");
$('#recording_container').children("video").removeAttr('controls');
$('#record_audio').children("video").removeAttr('controls');
$('#close-window-option').attr("onclick", "CloseStartedRecording();");
$('.completed-voice-option').addClass("hidden");
$('.completed-video-option').addClass("hidden");
if (type == 'video') {
var recording_parms = {
video: {
facingMode: 'user'
},
audio: true
};
} else {
var recording_parms = {
audio: true
};
}
//
// Now request permission and start recording.
$('#recording_element').removeClass('hidden');
// $('#stop-option').removeClass('hidden');
$('.play-option').addClass("hidden");
visualiserCount();
let stream = await navigator.mediaDevices.getUserMedia(recording_parms).then(function(stream) {
if (type == 'video') {
$('#stop-option').removeClass('hidden');
$('.video-error').addClass("hidden");
$('#recording_container').appendTo('#video-preview');
$('.video-message').addClass("active");
$('#stop-option').appendTo('#video-finish-option');
$('.video-option').addClass("hidden").children("text").text("Re-Record");
$('.back-option').addClass("hidden");
$('.recording-preview').removeClass("hidden");
var element = document.getElementById('recording_element');
element.muted = "muted";
element.play();
} else {
$('#stop-option').removeClass('hidden');
$('.voice-error').addClass("hidden");
$('#recording_container').appendTo('#voice-preview');
$('#stop-option').appendTo('#voice-finish-option');
$('.voice-option').addClass("hidden").children("text").text("Re-Record");
$('.back-option').addClass("hidden");
$('.visualiser').removeClass("completed").addClass("active");
var element = document.getElementById('recording_element');
element.muted = "muted";
element.play();
}
recordingHandler(stream);
}).catch(function(err) {
if (type == 'video') {
$('.video-error').removeClass("hidden");
} else {
$('.voice-error').removeClass("hidden");
}
});
async function recordingHandler(stream) {
recording.srcObject = stream;
recorder = new RecordRTCPromisesHandler(stream, {
type: type,
// mimeType: (type == 'video' ? 'video/mp4' : 'audio/webm'),
mimeType: BestFormat(type),
audioBitsPerSecond: 128000,
videoBitsPerSecond: 512000,
recorderType: MediaStreamRecorder,
disableLogs: true,
});
recorder.recordRTC.setRecordingDuration(60000, async function() {
StopRecording();
await recorder.stopRecording();
stopRecordingCallback();
});
await recorder.startRecording();
recorder.stream = stream;
const internalRecorder = await recorder.getInternalRecorder();
};
};
//
// What to do when we stop recording.
// document.getElementById('record_stop').onclick = StopRecording;
document.getElementById('record_stop').addEventListener("click", function() {
StopRecording();
});
async function StopRecording() {
if (recorder !== null) {
if (typeof recorder.getState === 'function') {
var is_recording = await recorder.getState();
if (is_recording == 'recording') {
await recorder.stopRecording();
}
await stopRecordingCallback(type);
}
}
$('#recording_container').children("video").attr('controls', 'controls');
$('.video-message').removeClass("active");
$('#stop-option').addClass('hidden');
$('#record_video_audio').removeClass('hidden');
$('#record_audio').removeClass('hidden');
$('.video-option').removeClass("hidden");
$('.voice-option').removeClass("hidden");
$('.back-option').removeClass("hidden");
$('.completed-voice-option').removeClass("hidden");
$('.completed-video-option').removeClass("hidden");
$('.recording-preview').addClass("hidden");
var element = document.getElementById('recording_element');
element.muted = false;
animation_done = true;
var visualiser_closed = setInterval(function() {
if (visualiser_count == animation_duration) {
$('.visualiser').removeClass("active").addClass("completed");
$('.play-option').removeClass("hidden");
clearInterval(visualiser_closed);
}
}, 1000);
};
//
// Handle the save.
async function stopRecordingCallback() {
$('#submit_form').addClass('hidden');
recording.srcObject = null;
let blob = await recorder.getBlob();
var blob_url = URL.createObjectURL(blob);
recording.src = blob_url;
recorder.stream.getTracks().forEach(t => t.stop());
let data_url = await recorder.getDataURL();
var datas = {
'type': type,
'content': data_url,
};
$('#submit').attr("disabled", "true");
const ret = await psLoad('', 'save_recorded_message', datas);
var parsed = JSON.parse(ret);
$('#attr_videomsg').val(parsed.location);
$('#submit_form').removeClass('hidden');
if (quiet == false) {
$('.video-message-notification').removeClass("hidden");
setTimeout(function() {
$('.video-message-notification').addClass("hidden");
}, 2000);
}
$('#submit').removeAttr("disabled");
await recorder.reset();
await recorder.destroy();
recorder = null;
}
</script>
<input type="hidden" id="attr_videomsg" name="attr_videomsg" value="">
<div class="video-message-notification hidden">
<div class="container">
<div class="notification">
<i class="fa-solid fa-folder-arrow-up"></i>
<p>Your recording has been saved and a copy will be sent with this product.</p>
</div>
</div>
</div>
<table class="message-card-seperator" width="100%">
<tbody>
<tr>
<td>
<hr style="background-color: #eee;">
</td>
<td style="width: 1px; padding: 0 0px; white-space: nowrap;">Or</td>
<td>
<hr style="background-color: #eee;">
</td>
</tr>
</tbody>
</table>
<!--Standard Card-->
<div class="col-xs-12">
<div class="standard_card">
<label for="standard" style="display:inline">
<div class="checkmark-container">
<input class="ignore non-pretty" checked="checked" type="radio" name="attr_card_type" id="standard" value="standard">
<p>Message Card</p>
</div>
</label>
<script>
$(document).ready(function() {
var text_max = '200';
$('#count_attr_messagecard').html(text_max + ' Characters Remaining');
$('#attr_messagecard').keyup(function() {
var text_length = $('#attr_messagecard').val().length;
var text_remaining = text_max - text_length;
$('#count_attr_messagecard').html(text_remaining + ' Characters Remaining');
});
});
</script>
<label for="attr_messagecard">
<span style="font-size: 0;">Standard Message Card</span> <textarea placeholder="" rows="4" maxlength="200" id="attr_messagecard" class="form-control" name="attr_messagecard" onkeyup="AddonCrossSell($(this).val());"></textarea>
</label>
<div id="count_attr_messagecard">200 Characters Remaining</div>
<div class="clearfix"></div>
</div>
</div>
<div class="clearfix"></div>
</div>
<script>
// Timer for when user stops typing
var typingTimer;
var doneTypingInterval = 400;
$("#attr_messagecard").on('keyup', function() {
clearTimeout(typingTimer);
typingTimer = setTimeout(GetSuggestedAddons, doneTypingInterval);
});
$("#attr_messagecard").on('keydown', function() {
clearTimeout(typingTimer);
});
var images_url = 'https://images.prestigeflowers.co.uk/';
var site_url = 'https://www.prestigeflowers.co.uk/'
// Get suggested addons function
function GetSuggestedAddons() {
// Get the occasions
var occasions = [{
"id": 12,
"occasion": "Anniversary"
}, {
"id": 11,
"occasion": "Birthday"
}, {
"id": 13,
"occasion": "Congratulations"
}, {
"id": 8,
"occasion": "Graduation"
}, {
"id": 7,
"occasion": "Thank"
}];
// Get the message
var message = $('#attr_messagecard').val();
// If the message is not empty
if (message.length != 0) {
// See if any of the occasions are in the message
var matched_occasions = [];
for (var i = 0; i < occasions.length; i++) {
if (message.toLowerCase().indexOf(occasions[i].occasion.toLowerCase()) > -1) {
matched_occasions.push(occasions[i].occasion);
}
}
// If there are any matched occasions
if (matched_occasions.length != 0) {
// Get the suggested addons
psLoad('', 'suggested_addons', {
occasions: matched_occasions
}).then(function(response) {
response = JSON.parse(response);
if (response.success) {
// Define the addons variable
;
// Get included addons
var included_addons = {
"1337": {
"id": 34,
"sort": 0,
"addon_name": "Premium Feed",
"pretty_name": "Premium Feed",
"image_name": "premium-flower-food.webp",
"price": 0,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "",
"xsell_img": "",
"is_customer_specific": "No",
"stock": 9716
}
};
// Get all addons allowed
var addons = [{
"id": 1007,
"sort": 0,
"addon_name": "180g Pralines",
"pretty_name": "Luxury Chocolates 180g",
"image_name": "PF_(AddOn)_Chocolate-RedPraline.jpg",
"price": 4.99,
"distributor": "julian",
"global_feature": 1,
"enabled": 1,
"tooltip": "Delicious praline chocolates, perfect for any occasion, may contain nuts*",
"xsell_img": "pf-popup-updated-ayr.png",
"is_customer_specific": "No",
"stock": 1438
}, {
"id": 1008,
"sort": 0,
"addon_name": "400g Pralines",
"pretty_name": "Deluxe Chocolates 400g",
"image_name": "PF_(AddOn)_Chocolate-BlackPraline.jpg",
"price": 9.99,
"distributor": "julian",
"global_feature": 1,
"enabled": 1,
"tooltip": "A box of delicious luxury praline chocolates, may contain nuts*",
"xsell_img": "PF-val-popup-chocs_2.png",
"is_customer_specific": "No",
"stock": 1592
}, {
"id": 5,
"sort": 0,
"addon_name": "Clear Vase",
"pretty_name": "Clear Vase",
"image_name": "new-clear-vase.png",
"price": 4.99,
"distributor": "julian",
"global_feature": 1,
"enabled": 1,
"tooltip": "A clear and versatile glass vase perfect for this bouquet",
"xsell_img": "vase.jpg",
"is_customer_specific": "No",
"stock": 9999
}, {
"id": 1049,
"sort": 0,
"addon_name": "PP2202",
"pretty_name": "Afternoon Tea Hamper",
"image_name": "aft-tea-postal-ENJOY.jpg",
"price": 16.99,
"distributor": "julian",
"global_feature": 1,
"enabled": 1,
"tooltip": "Delicious treats hamper perfect for celebrating",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 7019
}, {
"id": 1058,
"sort": 0,
"addon_name": "Guylian Chocolates",
"pretty_name": "Guylian Chocolates",
"image_name": "PF_(AddOn)_Chocolate-guylian.jpg",
"price": 2.99,
"distributor": "julian",
"global_feature": 1,
"enabled": 1,
"tooltip": "Delicious Guylian chocolates the perfect accompaniment with flower. Weighs 42g",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 4696
}, {
"id": 1055,
"sort": 0,
"addon_name": "Birthday Cake",
"pretty_name": "Birthday Cake",
"image_name": "PF_(AddOn)_Gift-BirthdayCake.jpg",
"price": 4.99,
"distributor": "julian",
"global_feature": 1,
"enabled": 1,
"tooltip": "A delicious hand made birthday cake by Valley Bakery.",
"xsell_img": "birthday-cake-extra.jpg",
"is_customer_specific": "No",
"stock": 6839
}, {
"id": 1051,
"sort": 0,
"addon_name": "Cuddly Bear",
"pretty_name": "Luxury Teddy Bear",
"image_name": "PF_(AddOn)_Bear.jpg",
"price": 9.99,
"distributor": "julian",
"global_feature": 1,
"enabled": 1,
"tooltip": "Limited Edition handmade teddy ready for his new home!\n",
"xsell_img": "",
"is_customer_specific": "No",
"stock": 5652
}, {
"id": 1072,
"sort": 0,
"addon_name": " Premium Assorted Belgian Chocolates - Round Box",
"pretty_name": "Premium Belgian Chocolates",
"image_name": "PF_(AddOn)_Chocolate-LuxuryRound.jpg",
"price": 19.99,
"distributor": "julian",
"global_feature": 1,
"enabled": 1,
"tooltip": "Delicious hand-crafted Belgian chocolates, perfect for elevating any occasion, this product contains alcohol",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 6998
}, {
"id": 1159,
"sort": 0,
"addon_name": "Just For You Add On Chocolate ",
"pretty_name": "Just For You Chocolate Bar",
"image_name": "PF_JustForYou-60gChocBar_AddOn(600px-300dpi).jpg",
"price": 4.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "Delicious handcrafted Milk Chocolate 'Just For You' Valley Chocolate Bar (60g)",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9999
}, {
"id": 1121,
"sort": 0,
"addon_name": "Tea And Biscuits",
"pretty_name": "Tea and Biscuits",
"image_name": "new tea and biscuits.jpg",
"price": 3.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "Delicious flower shaped biscuits with Yorkshire Tea Bags",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9999
}, {
"id": 61,
"sort": 0,
"addon_name": "Sparkling French Wine",
"pretty_name": "Sparkling French Wine (75cl)",
"image_name": "PF_(AddOn)_Drinks-Pigalle.jpg",
"price": 19.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "An elegant and refined bottle of bubbly",
"xsell_img": "",
"is_customer_specific": "No",
"stock": 8797
}, {
"id": 1113,
"sort": 0,
"addon_name": "Happy Birthday Postal",
"pretty_name": "Happy Birthday Gift Box",
"image_name": "PF_(AddOn)_PostalHamper-Birthday-Birthday.jpg",
"price": 18.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "The ultimate Happy Birthday gift with cake, candles & treats!",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9431
}, {
"id": 1158,
"sort": 0,
"addon_name": "Add On Happy Birthday Chocolate ",
"pretty_name": "Birthday Chocolate Bar",
"image_name": "PF_HappyBirthday-60gChocBar_AddOn(600px-300dpi).jpg",
"price": 4.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "Delicious handcrafted Milk Chocolate 'Happy Birthday' Valley Chocolate Bar (60g)",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9999
}, {
"id": 1135,
"sort": 0,
"addon_name": "Spring Yankee Candle",
"pretty_name": "Garden Blooms Candle",
"image_name": "PF_(AddOn)_Candle-YankeeGardenBlooms.jpg",
"price": 9.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "A beautiful Garden Blooms Yankee Candle, for all occasions!",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 8934
}, {
"id": 1116,
"sort": 0,
"addon_name": "Afternoon Tea Gardening Postal",
"pretty_name": "Afternoon Tea Gardening Gift",
"image_name": "postal addons_0009_PH-Sent-with-love.jpg",
"price": 16.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "A lovely unique gift for nature lovers, artisan cakes, tea & seasonal planting bulbs! ",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9719
}, {
"id": 1076,
"sort": 0,
"addon_name": "Daffodil Bulbs",
"pretty_name": "Daffodil Bulbs",
"image_name": "PF--Daffodils-bulb-add-on (1).jpg",
"price": 4.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "10 Daffodil Bulbs ready for planting!",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9999
}, {
"id": 1048,
"sort": 0,
"addon_name": "PP2208",
"pretty_name": "Wine Hamper",
"image_name": "wine-postal-ENJOY.jpg",
"price": 16.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "Cheers to all occasions with this wine gift!",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9801
}, {
"id": 1155,
"sort": 0,
"addon_name": "New Champagne 75cl Add On",
"pretty_name": "Champagne 75cl",
"image_name": "PF_(AddOn)_Drinks-BaronVilleboerg.jpg",
"price": 34.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "Luxurious Baron De Villeboerg Champagne perfect for celebrating!",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9839
}, {
"id": 1157,
"sort": 0,
"addon_name": "GIN + TONIC CANDLE ADD ON",
"pretty_name": "Gin + Tonic Candle",
"image_name": "PF--Gin-and-Tonic-candle-add-on.jpg",
"price": 5.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "A beautiful Gin & Tonic scented candle, for all occasions!",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9999
}, {
"id": 13,
"sort": 0,
"addon_name": "White Wine 37.5cl",
"pretty_name": "White Wine (37.5cl)",
"image_name": "PF_(AddOn)_Drinks-WhiteWine37cl.jpg",
"price": 7.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "Smooth, aromatic and refreshing French white wine, perfect to say Cheers! ",
"xsell_img": "",
"is_customer_specific": "No",
"stock": 9591
}, {
"id": 1039,
"sort": 0,
"addon_name": "Eco Clear Vase",
"pretty_name": "Eco Clear Vase",
"image_name": "luxe-vase.jpg",
"price": 8.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "New! Eco handmade vase made from recycled glass\n",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9559
}, {
"id": 1089,
"sort": 0,
"addon_name": "Dark Chocolate 90g Valley Chocolate ",
"pretty_name": "Dark Chocolate & Raspberry Bar",
"image_name": "PF_(AddOn)_Chocolate-DarkRaspberryBar.jpg",
"price": 4.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "Delicious handcrafted Dark Chocolate and Raspberry Valley Chocolate Bar (90g)",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9644
}, {
"id": 1017,
"sort": 0,
"addon_name": "Birthday Pick",
"pretty_name": "Happy Birthday Pick",
"image_name": "birthday_pick.png",
"price": 2.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "Add a delightful pick for extra surprise\n",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9689
}, {
"id": 14,
"sort": 0,
"addon_name": "Red Wine 37.5cl",
"pretty_name": "Red Wine (37.5cl)",
"image_name": "PF_(AddOn)_Drinks-RedWine37cl.jpg",
"price": 7.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "A rich and full bodied delicious French red wine perfect to say Cheers! ",
"xsell_img": "",
"is_customer_specific": "No",
"stock": 9661
}, {
"id": 1140,
"sort": 0,
"addon_name": "Happy Birthday Candle Add On 2024",
"pretty_name": "Happy Birthday Candle ",
"image_name": "PF_(AddOn)_Candle-HappyBirthday.jpg",
"price": 2.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "A colourful Happy Birthday candle to make their day extra special!",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9935
}, {
"id": 1117,
"sort": 0,
"addon_name": "Lullaby Baby Postal",
"pretty_name": "New Baby Gift",
"image_name": "PF_(AddOn)_PostalHamper-NewBaby-NewBaby.jpg",
"price": 24.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "The perfect gift to celebrate a new baby with lovely lamb toys!",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9954
}, {
"id": 1044,
"sort": 0,
"addon_name": "PP2204",
"pretty_name": "Perfect Pamper Gift",
"image_name": "pamper-hamper-ENJOY.jpg",
"price": 16.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "A delightful pamper hamper filled with goodies!",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9552
}, {
"id": 111,
"sort": 0,
"addon_name": "Hand Blown Vase",
"pretty_name": "Hand Blown Vase",
"image_name": "hand_blown_vase.png",
"price": 9.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "Handmade artisan vase, each unique\n",
"xsell_img": "",
"is_customer_specific": "No",
"stock": 9862
}, {
"id": 1154,
"sort": 0,
"addon_name": "Lemon Slices - Valley Sweets",
"pretty_name": "Fizzy Lemon Sweets",
"image_name": "PF_(AddOn)_Sweets-FizzyLemon.jpg",
"price": 3.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "Delicious fizzy lemon sweet slices by Valley Sweets",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9960
}, {
"id": 1167,
"sort": 0,
"addon_name": "PP2206",
"pretty_name": "Chocolate Gift Box Gift",
"image_name": "6eb9b3858437161c45549380690e1975.jpg",
"price": 16.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9999
}, {
"id": 1141,
"sort": 0,
"addon_name": "Moet 75cl",
"pretty_name": "Moet 75cl",
"image_name": "PF-MOET-75.CL.jpg",
"price": 65,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "The perfect extra for special occasions, delightful bottle of Moet 75cl Champagne",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9857
}, {
"id": 1046,
"sort": 0,
"addon_name": "Scented Occasion Candle",
"pretty_name": "Occasion Candle",
"image_name": "occasion-candle.png",
"price": 4.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "Beautifully frangrant candle, florist selected for the occasion\n",
"xsell_img": "",
"is_customer_specific": "No",
"stock": 9788
}, {
"id": 1077,
"sort": 0,
"addon_name": "Tulip Bulbs Queen of the Night",
"pretty_name": "Tulip Planting Bulbs",
"image_name": "PF-Tulip-bulb-addon (1).jpg",
"price": 4.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "10 Tulip Bulbs ready for planting!",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9999
}, {
"id": 15,
"sort": 0,
"addon_name": "Moet 37.5cl",
"pretty_name": "Moet (37.5cl)",
"image_name": "half-moet.jpg",
"price": 39.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "The perfect extra gift to make any occasion that much more extraordinary",
"xsell_img": "",
"is_customer_specific": "No",
"stock": 9746
}, {
"id": 58,
"sort": 0,
"addon_name": "Gin 5cl + Tonic",
"pretty_name": "Gin 5cl + Tonic",
"image_name": "PF_(AddOn)_Drinks-GinTonic.jpg",
"price": 4.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "A classic cocktail drink perfect for celebrating ",
"xsell_img": "",
"is_customer_specific": "No",
"stock": 9576
}, {
"id": 1145,
"sort": 0,
"addon_name": "PP2202 Happy Birthday Postcard",
"pretty_name": "Happy Birthday Afternoon Tea",
"image_name": "PF_(AddOn)_PostalHamper-AfternoonTea-Birthday.jpg",
"price": 16.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "Delectable Afternoon Tea postal gift, perfect for all occasions! ",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9881
}, {
"id": 1043,
"sort": 0,
"addon_name": "PP2205",
"pretty_name": "Gin and Tonic Gift",
"image_name": "PH-Gin-2024-Enjoy.jpg",
"price": 16.99,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "A beautiful hamper for gin & tonic lovers!",
"xsell_img": null,
"is_customer_specific": "No",
"stock": 9986
}, {
"id": 34,
"sort": 0,
"addon_name": "Premium Feed",
"pretty_name": "Premium Feed",
"image_name": "premium-flower-food.webp",
"price": 0,
"distributor": "julian",
"global_feature": 0,
"enabled": 1,
"tooltip": "",
"xsell_img": "",
"is_customer_specific": "No",
"stock": 9716
}]
var addons = addons.reduce(function(map, obj) {
map[obj.id] = obj;
return map;
}, {});
// Get the suggested addons
var suggested_addons = response.suggested_addons;
// Loop through suggested addons and find the addon info
var matched_addons = [];
for (var i = 0; i < suggested_addons.length; i++) {
var addon = addons[suggested_addons[i]];
matched_addons.push(addon);
}
// Get a list of currently selected addons
var selected_addons = [];
$('input[name^="attr_addons_"]').each(function() {
if ($(this).prop('checked') == true) {
selected_addons.push($(this).attr('name'));
}
});
// Check if any of the matched addons are undefined and remove them
for (var i = matched_addons.length - 1; i >= 0; --i) {
if (matched_addons[i] == undefined) {
matched_addons.splice(i, 1);
}
}
// if there are any matched addons
if (matched_addons.length != 0) {
// Only get the first 5 addons
matched_addons = matched_addons.slice(0, 4);
// Foreach matched_addon add it to the .suggested-addons
var html = '';
for (var i = 0; i < matched_addons.length; i++) {
// Remove spaces from addon_name
var new_addon_name = matched_addons[i].addon_name.replace(/\s/g, '');
// Set defaults for checked, readonly and price
var checked = '';
var readonly = '';
var price = '£' + parseFloat(matched_addons[i].price).toFixed(2);
// Set the select_addon variable
var select_addon = 'attr_addons_' + matched_addons[i].id + '_' + matched_addons[i].addon_name;
// If the addon is included
if (included_addons[matched_addons[i].id] != undefined) {
checked = 'checked';
readonly = 'disabled';
price = 'Included!';
}
// If the addon is already selected
if (selected_addons.indexOf(select_addon) > -1) {
checked = 'checked';
}
// Build our html
html += '<div class="suggested-addon">';
html += '<label class="checkmark-container" for="suggested_attr_addons_' + matched_addons[i].id + '_' + matched_addons[i].addon_name + '">';
html += '<div class="suggested-addon-image">';
html += '<img class="suggested-addons-image lazy" src="' + images_url + 'fetch/w_110,e_sharpen:80,q_auto,f_auto,dpr_auto/' + site_url + 'images/attributes/' + matched_addons[i].image_name + '">';
html += '</div>';
html += '<div class="suggested-addon-name">';
html += matched_addons[i].pretty_name;
html += '</div>';
html += '<div class="addon-price">';
html += '<strong>' + price + '</strong>';
html += '</div>';
html += '<input onChange="checkAddon(\'' + select_addon + '\');" class="suggested-addon-checkmark" type="checkbox" ' + checked + ' ' + readonly + ' toggle-id="suggested_attr_addons_' + matched_addons[i].id + '_' +
new_addon_name + '" id="suggested_attr_addons_' + matched_addons[i].id + '_' + matched_addons[i].addon_name + '" name="suggested_attr_addons_' + matched_addons[i].id + '_' + matched_addons[i].addon_name + '" />';
html += '<span class="checkmark"></span>';
html += '</label>';
html += '</div>';
}
// Add the html to the .suggested-addons and remove the hidden class
$('.suggested-addons').html(html);
$('.occasion-addon-window').removeClass('hidden');
}
}
});
} else {
// No matched occasions so remove the html and add the hidden class
$('.suggested-addons').html('');
$('.occasion-addon-window').addClass('hidden');
}
} else {
// Message is empty so remove the html and add the hidden class
$('.suggested-addons').html('');
$('.occasion-addon-window').addClass('hidden');
}
}
// Check addon function
function checkAddon(id) {
// Get the toggle
var toggle_id = $('input[name="' + id + '"]');
// If the toggle is checked
if (toggle_id.prop('checked') == true) {
toggle_id.prop('checked', false);
} else {
toggle_id.prop('checked', true);
}
}
</script>
<div class="occasion-addon-window hidden">
<div class="occasion-step">
<span class="stage">
<i class="fa-regular fa-gift"></i>
</span>
<span class="product-step-title"> Suggested addons </span>
</div>
<div class="suggested-addons"></div>
</div>
</div>
<script>
$(document).ready(function() {
$('#submit').click(function(e) {
if ($('#personalised').is(':checked') && !$('#attr_personalised').val()) {
e.preventDefault();
window.alert("You haven't personalised your card. Please click Personalise to customise your card, or add a message card instead.");
return false;
}
OpenCrossSell();
});
})
</script>
<script>
var pathArray = window.location.pathname.split('/');
</script>
<!-- Delivery Date -->
<div class="product-step-box step2 mandatory">
<div class="product-step datepicker-trigger">
<span class="stage">
<i class="fa-regular fa-calendar"></i>
</span>
<span class="product-step-title"> Choose a Delivery Date </span>
</div>
<div class="step-content">
<!-- <style>
body.product_page .datepicker{
display: none !important;
}
</style> -->
<div class="form-group list-picker">
<label for="delivery_date">Date</label>
<div class="datepicker-container">
<input id="delivery_date" name="attr_deliverydate" class="form-control datepicker-trigger" value="Thursday, 07th November, 2024">
<img class="datepicker-trigger" alt="Calendar Open" src="https://images.prestigeflowers.co.uk/fetch/https://www.prestigeflowers.co.uk/images/assets/calendar.jpg">
</div>
</div>
<script>
$(function() {
var product_id = '5709';
var input = $('#delivery_date');
var key = 0;
input.on('change', function() {
var delivery_date_selected = input.val();
var datas = delivery_date_selected + '|' + product_id + '|' + key;
psLoad('delivery_option_result', 'delivery_options', datas);
});
psLoad('delivery_option_result', 'delivery_options', input.val() + '|' + product_id + '|' + key);
});
</script>
<!--END DELIVERY DATE-->
<style>
</style>
<!--START PEAK CALENDAR CTA-->
<!--END PEAK CALENDAR CTA-->
<div class="next-3-days">
<span class="next-3-day" data-date="07-11-2024">Thursday, 07, Nov</span>
<span class="next-3-day" data-date="08-11-2024">Friday, 08, Nov</span>
<span class="next-3-day" data-date="09-11-2024">Saturday, 09, Nov</span>
</div>
<div id="delivery_option_result">
<div class="delivery-option premium-delivery paid-delivery">
<div class="radio-button">
<input type="radio" name="attr_deliverymethod_0" id="attr_deliverymethod_0_0" checked="checked" value="Premium Delivery" onchange="SaveCart(0);">
<label for="attr_deliverymethod_0_0">
<strong>Premium Delivery</strong>
<p><strong>Tracked Named Day Delivery (Recommended) - Order by 10pm</strong> <br> Delivered the Next Day. Track your order online & receive SMS updates</p>
<span>£5.99</span>
</label>
</div>
</div>
<div class="delivery-option before-1pm paid-delivery">
<div class="radio-button">
<input type="radio" name="attr_deliverymethod_0" id="attr_deliverymethod_1_0" value="Before 1pm" onchange="SaveCart(0);">
<label for="attr_deliverymethod_1_0">
<strong>Before 1pm</strong>
<p><strong>Guaranteed before 1pm (Recommended) - Order by 2pm</strong> <br> Delivered the Next Day before 1pm. Track your order online & receive SMS updates (requires a signature)</p>
<span>£8.99</span>
</label>
</div>
</div>
<script>
if (typeof clearDelivery !== 'undefined') {
clearDelivery();
}
</script>
</div>
<div class="clearfix mrg20B"></div>
</div>
</div>
<!-- END Delivery Date -->
<div class="step-content">
<div class="afix add-cart-icon" data-spy="affix" data-offset-top="2100">
<input type="hidden" name="csrf_token" value="MTczMDg1MTQyNlBadHpWVVd4NnpNQzE4b0hLdDE0OWhyV1V3Nkd2WlN5"> <input type="hidden" id="productid" name="productid" value="5709">
<input type="hidden" id="attr_premiummsg" name="attr_premiummsg">
<input type="button" name="submit" id="submit" class="add-cart btn" onclick="PinTracking();" value="Order Now »">
<div id="addon_cross_sell_container" active="yes">
<div id="addon_cross_sell_wrapper">
<div class="addons-cross-sell">
<div onclick="CloseCrossSell();" class="close-button">X</div>
<span class="addon_id hidden">1007</span>
<img src="https://images.prestigeflowers.co.uk/fetch/https://www.prestigeflowers.co.uk//images/something-extra/pf-popup-updated-ayr.png" alt="Add a little something extra" class="addon-img">
<hr>
<div class="row">
<div class="col-12 col-md-7">
<h3>Special Offer: Add a Luxury Chocolates 180g +£4.99</h3>
</div>
<div class="col-12 col-md-5">
<div class="row">
<div class="col-12 col-md-6">
<input type="submit" class="btn error" value="No Thanks">
</div>
<div class="col-12 col-md-6">
<input type="submit" class="btn success" onclick="ToggleAddon('attr_addons_1007_180gPralines');" value="Yes Please">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
POST https://www.prestigeflowers.co.uk/search
<form method="POST" action="https://www.prestigeflowers.co.uk/search">
<select name="search_box" placeholder="All Categories">
<option value="">All Categories</option>
<option value="Anniversary Flowers">Anniversary Flowers</option>
<option value="Birthday Flowers">Birthday Flowers</option>
<option value="Congratulations Flowers">Congratulations Flowers</option>
<option value="Get Well">Get Well</option>
<option value="Just Because">Just Because</option>
<option value="New Baby Flowers">New Baby Flowers</option>
<option value="Next Day Flowers">Next Day Flowers</option>
<option value="Thank You Flowers">Thank You Flowers</option>
<option value="Valentines Day Flowers">Valentines Day Flowers</option>
</select>
<select name="price_range" placeholder="All Prices">
<option value="0">All Prices</option>
<option value="0-30">Below £30</option>
<option value="30-40">£30 to £40</option>
<option value="40-50">£40 to £50</option>
<option value="50-0">Above £50</option>
</select>
<button type="submit" class="btn btn-block">Find</button>
</form>
<form>
<div class="errors">
<label for="nl-name"></label>
</div>
<div class="form-group name">
<input type="text" class="form-control name-box" id="nl-name" aria-describedby="emailHelp" placeholder="Name">
</div>
<div class="errors">
<label for="nl-email"></label>
</div>
<div class="form-group email">
<input type="email" class="form-control email-box" id="nl-email" placeholder="Email Address">
</div>
<!-- <button type="button" class="btn get-code">Subscribe</button> -->
<div class="newsletter-birthday-container">
<p for="nl-date-of-birth" class="birthday-text">Why not add your Birthday, so we know when to send you a free gift!</p>
<div class="newsletter-birthday-fields">
<input type="date" max="2024-11-06" class="form-control birthday-box" id="nl-date-of-birth" placeholder="Date of Birth">
</div>
</div>
<button type="button" class="g-recaptcha btn get-code" data-sitekey="6Lc3w6kZAAAAAJBuVZeCy9sh3HhaeOkW7PiMVHvu" data-badge="inline" data-size="invisible" data-callback="newsletterSubscribe" data-action="submit"
style="color: #ffffff; background: #000000;">Sign Up</button>
<button type="button" class="btn no-thanks" data-dismiss="modal" style="color: #fff !important;">No, Thank You</button>
</form>
Text Content
verifying... Need help? Call us for free on 0344 310 5555 0 Excellent Welcome to Prestige Flowers Sign In or Register My Account Order Tracking Customer Service -------------------------------------------------------------------------------- Your Basket Is Empty * Next Day Flowers NEXT DAY FLOWERS * Next Day Flowers * Birthday Flowers * Autumn Flowers * Flowers with Free Chocs * Florist Specials * Luxury Flowers * Charity Flowers * Back * Birthday Flowers BIRTHDAY FLOWERS * Birthday Flowers * Birthday Gifts * Birthday Flowers With Cards * Birthday Gift Sets * Postal Gifts * Back * By Occasion BY OCCASION * Birthday Flowers * Sympathy Flowers * Anniversary Flowers * Romantic Flowers * Thank You Flowers * Get Well Soon Flowers * Congratulations Flowers * New Baby Flowers * Christmas Flowers * Back * Christmas CHRISTMAS * Christmas Flowers * Christmas Flowers with Card * Luxury Christmas Flowers * Christmas Plants * Letterbox Christmas Flowers * Christmas Flowers with Fizz * Christmas Flowers with Chocolate * Christmas Flowers with Postal Gifts * Christmas Hampers * Christmas Plant Gifts * Back * Luxury LUXURY * Luxury Flowers * Haute Florist © * Hat Boxes * Flowers & Champagne * Castle Howard Collection * National Gallery Flowers * Back * Letterbox Flowers LETTERBOX FLOWERS * All Letterbox Flowers * Letterbox Subscription * Back * Plants PLANTS * Plants * Christmas Plants * Autumn Plants * Indoor Plants * Plant Gifts * Artificial Plants * Outdoor Plants * Back * Gifts GIFTS * Autumn Gifts * Bouquets & Gift Sets * Flowers with Vase * Flowers with Champagne * Flowers with Chocolates * Postal Gifts * Plant Gifts * Christmas Hampers * Back * Christmas Hampers CHRISTMAS HAMPERS * Christmas Hampers * Christmas Family Gifts * Luxury Christmas Hampers * Christmas Chocolate Gifts * Non Alcoholic Hampers * Christmas Gifts Under £50 * Christmas Eve Boxes * Secret Santa * Back * Flowers with Card FLOWERS WITH CARD * Flowers with Card * Birthday Flowers with Card * Back * Subscriptions * * Login * Register * Track Your Order * Customer Service * Close Menu International Flowers ▼ * Australia * Belgium * Canada * Denmark * France * Germany * Holland * Republic of Ireland * Italy * Japan * Luxembourg * Philippines * Singapore * Spain * United States * United Kingdom * All Countries ORDER BY 10PM FOR NEXT DAY DELIVERY Loading... Delivery from Thursday Order Now! Product Code: CRF005 * MAJESTIC £50 Description Substitution Delivery Info A glorious selection of luxurious red Avalanche Roses arranged along with golden orbits and foliage for a superb floral masterpiece. All professionally arranged in a stunning Prestige Luxury Collection bag, this bunch will impress anyone lucky enough to be on the receiving end. 25% of the value of this gift will be donated to Cancer Research UK to fund research for beating cancer. Due to seasonal availability of certain flowers and colours from time to time, we may use a substitution. In cases like this, we will always use the best match with the stem pictured, and it will always be of equal or higher value to the one pictured. In the rare case that no suitable substitution exists we will make contact with you to find an alternative solution. This product is delivered by trackable courier. Kindly note that some non-mainland areas might require an additional working day for transit. Adverse weather conditions can also result in delays. Deliveries might be early during particular events. For more details, feel free to contact us, and one of our customer care agents will provide you with further details before you place your order. YOU MIGHT ALSO LIKE THESE Lily Rose Medley £27 Add to Basket Order Now » Next Day Delivery Save £25 Rose and Lily £49.99 £24.99 Add to Basket Order Now » Next Day Delivery Save £25 Amaryllis Allure £59.99 £34.99 Add to Basket Order Now » Next Day Delivery Add a Little Extra? Premium Feed FREE! Premium Feed Luxury Chocolates 180g £4.99 Luxury Chocolates 180g Deluxe Chocolates 400g £9.99 Deluxe Chocolates 400g Clear Vase £4.99 Clear Vase Afternoon Tea Hamper £16.99 Afternoon Tea Hamper Guylian Chocolates £2.99 Guylian Chocolates Birthday Cake £4.99 Birthday Cake Luxury Teddy Bear £9.99 Luxury Teddy Bear Premium Belgian Chocolates £19.99 Premium Belgian Chocolates Just For You Chocolate Bar £4.99 Tea and Biscuits £3.99 Sparkling French Wine (75cl) £19.99 Happy Birthday Gift Box £18.99 Birthday Chocolate Bar £4.99 Garden Blooms Candle £9.99 Afternoon Tea Gardening Gift £16.99 Daffodil Bulbs £4.99 Wine Hamper £16.99 Champagne 75cl £34.99 Gin + Tonic Candle £5.99 White Wine (37.5cl) £7.99 Eco Clear Vase £8.99 Dark Chocolate & Raspberry Bar £4.99 Happy Birthday Pick £2.99 Red Wine (37.5cl) £7.99 Happy Birthday Candle £2.99 New Baby Gift £24.99 Perfect Pamper Gift £16.99 Hand Blown Vase £9.99 Fizzy Lemon Sweets £3.99 Chocolate Gift Box Gift £16.99 Moet 75cl £65 Occasion Candle £4.99 Tulip Planting Bulbs £4.99 Moet (37.5cl) £39.99 Gin 5cl + Tonic £4.99 Happy Birthday Afternoon Tea £16.99 Gin and Tonic Gift £16.99 Write a Message Card Add a Personalised Card * Luxury hand finished cards * Cards for all occasions * Choose from many designs * Easily upload your pictures + £3.99 £5.99 Send a FREE recorded message Record your message We'll send with your gift Record Now! SEND YOUR FREE VIDEO MESSAGE WITH EVERY GIFT RECORD A VOICE MESSAGE Just press the 'Record' button to start recording your voice message, you will have up to 30 seconds. PLEASE ALLOW ACCESS TO YOUR CAMERA AND MICROPHONE TO START RECORDING. PLEASE ALLOW ACCESS TO YOUR MICROPHONE TO START RECORDING. LET'S GET STARTED HAPPY WITH YOUR RECORDING? Back Add To Order! Save & Review Record Video Message Voice Message Close Back Record Save & Send Stop Recording Your recording has been saved and a copy will be sent with this product. -------------------------------------------------------------------------------- Or -------------------------------------------------------------------------------- Message Card Standard Message Card 200 Characters Remaining Suggested addons Choose a Delivery Date Date Thursday, 07, Nov Friday, 08, Nov Saturday, 09, Nov Premium Delivery Tracked Named Day Delivery (Recommended) - Order by 10pm Delivered the Next Day. Track your order online & receive SMS updates £5.99 Before 1pm Guaranteed before 1pm (Recommended) - Order by 2pm Delivered the Next Day before 1pm. Track your order online & receive SMS updates (requires a signature) £8.99 X 1007 -------------------------------------------------------------------------------- SPECIAL OFFER: ADD A LUXURY CHOCOLATES 180G +£4.99 Description Substitution Delivery Info A glorious selection of luxurious red Avalanche Roses arranged along with golden orbits and foliage for a superb floral masterpiece. All professionally arranged in a stunning Prestige Luxury Collection bag, this bunch will impress anyone lucky enough to be on the receiving end. 25% of the value of this gift will be donated to Cancer Research UK to fund research for beating cancer. Due to seasonal availability of certain flowers and colours from time to time, we may use a substitution. In cases like this, we will always use the best match with the stem pictured, and it will always be of equal or higher value to the one pictured. In the rare case that no suitable substitution exists we will make contact with you to find an alternative solution. This product is delivered by trackable courier. Kindly note that some non-mainland areas might require an additional working day for transit. Adverse weather conditions can also result in delays. Deliveries might be early during particular events. For more details, feel free to contact us, and one of our customer care agents will provide you with further details before you place your order. YOU MIGHT ALSO LIKE THESE Breast Cancer Awareness Bouquet £26 Add to Basket Order Now » Next Day Delivery Lily Rose Medley £27 Add to Basket Order Now » Next Day Delivery Special Birthday £40 Add to Basket Order Now » Next Day Delivery Reviews Product Finder All Categories Anniversary Flowers Birthday Flowers Congratulations Flowers Get Well Just Because New Baby Flowers Next Day Flowers Thank You Flowers Valentines Day Flowers All Prices Below £30 £30 to £40 £40 to £50 Above £50 Find Copyright © 2024 Prestige Flowers™ Follow Us On: Get our special offers * About Us * Contact Us * Terms & Conditions * Privacy Policy * 100% Satisfaction Guarantee * Delivery Information * Business Opportunities * Flower Care * Plant Care * Student Discount Proud to be sustainable and one of the UK's most ethical florists - See The Stats * FLOWER DELIVERY -------------------------------------------------------------------------------- * Mother's Day Flowers * Valentine's Day Flowers * Christmas Flowers * International Mother's Day * NEXT DAY FLOWERS -------------------------------------------------------------------------------- * Next Day Flowers * Anniversary Flowers * Get Well Soon Flowers * Things You Never Knew About Flowers * 100% SATISFACTION GUARANTEE -------------------------------------------------------------------------------- * Cut flowers – which ones last the longest Thu, 04 Jan 2024 * Flower buds and how to help them open Mon, 16 Oct 2023 -------------------------------------------------------------------------------- Cookies? We are not the cookie monster, we use cookies to allow us to provide the best experience to our customers, by browsing our site you are agreeing to accept these cookies. ✕ SIGN UP AND GET 10% OFF YOUR FIRST ORDER Simply add your name and email below to recieve exclusive offers and news, as well as 10% off your first order! Why not add your Birthday, so we know when to send you a free gift! Sign Up No, Thank You Click here to view our Privacy Policy This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply. By using our services you agree to our use of cookies to improve your shopping experience. View Privacy Policy Accept Privacy Policy SEARCH FOR PRODUCTS We will show you all available products which are related to your query. Chat with us, powered by LiveChat